シングルトンパターンの概念
スキーマ定義
クラスのインスタンスが 1 つだけであることを保証し、そのインスタンスへのグローバル アクセス ポイントを提供します。
お腹を空かせたシングルトンと怠け者のシングルトン
一般的なシングルトン パターンには、饿汉单例
と の2 つの分岐があります懒汉单例
。
- ハングリー シングルトンとは、プログラムの初期化時にシングルトン オブジェクトが作成されることを意味します。利点はオブジェクトを使用するときに直接取得できることですが、欠点はオブジェクトを呼び出すかどうかに関係なく作成されてメモリを占有することです。
- 遅延シングルトンとは、シングルトン オブジェクトが初めて呼び出されたときに、対応するオブジェクトが作成されることを意味します。その利点は、呼び出されないオブジェクトが作成されないことですが、欠点は、オブジェクトの作成に時間がかかり、その結果、最初の呼び出しが特殊になり、オブジェクトの作成時にスレッドセーフではないことです。
推奨事項:
シングルトンを使用する必要があり、シングルトン オブジェクトの初期化はメイン スレッドの先頭で直接実行する必要があります。
これには多くの利点があります。第一に、オブジェクトの作成時にオブジェクトが生成されないことが保証され、竞争条件(race condition)
コードの記述が単純明快になります (メインスレッドで作成されない場合、複数のスレッドが同時にオブジェクトを作成することを検討する必要があります。この場合、ミューテックスなどの手段で制限する必要があります。C++11 の static キーワードはオブジェクトを 1 つだけ作成することを保証できますが、それでも推奨されません)。第二に、オブジェクトを取得する時間が固定され、最初の呼び出しに余分な時間がかからなくなります。怠惰なモード。
シングルトンパターンの実装
1. 最も基本的なハングリーマンシングルトンモードの実装
#include <iostream>
class SingletonClass {
public:
//单例对象的全局访问点
static SingletonClass* getInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new SingletonClass();
return m_pInstance;
}
std::cout << "对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//销毁单例对象
static void destoryInstance() {
delete m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
return;
}
private:
//构造函数
SingletonClass() {
std::cout << "SingletonClass的单例对象被创建" << std::endl;
}
//析构函数
~SingletonClass() {
std::cout << "SingletonClass的单例对象被销毁" << std::endl;
}
//禁止拷贝
SingletonClass(const SingletonClass&) = delete;
SingletonClass operator=(const SingletonClass&) = delete;
static SingletonClass* m_pInstance; //单例对象的声明
};
SingletonClass* SingletonClass::m_pInstance = nullptr; //单例对象的定义
int main() {
//在主线程最开始的地方就创建出单例对象,相当于饿汉单例
SingletonClass::getInstance();
//...
//多次调用,获取的都是同一个对象
SingletonClass* obj1 = SingletonClass::getInstance();
SingletonClass* obj2 = SingletonClass::getInstance();
SingletonClass* obj3 = SingletonClass::getInstance();
//...
//最后要手动销毁单例对象
SingletonClass::destoryInstance();
return 0;
}
输出:
SingletonClass的单例对象被创建
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
SingletonClass的单例对象被销毁
2. 追加のインターフェイスを提供して、Hungry Singleton をインターフェイス設計に反映できるようにします。
1 では、実現したいのはハングリー シングルトンですが、特定のインターフェイスが提供されていないため、プログラマは論理的な正しさを保証するために main 関数で必ず 1 回呼び出されるようにする必要がありますが、これは明らかに不適切です。したがって、ハングリー シングルトン オブジェクトを特別に作成するためのインターフェイスを追加できます。シングルトン オブジェクトが作成される前にハングリー シングルトン オブジェクトを取得しようとすると例外がスローされ、これがハングリー シングルトンであり、main 関数で作成する必要があることをプログラマに思い出させますSingletonClass::getInstance();
。
#include <iostream>
#include <exception>
class SingletonClass {
public:
//单例对象的全局访问点
static SingletonClass* getInstance() {
if (m_pInstance == nullptr) {
throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
}
std::cout << "对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//创建单例对象
static void constructInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new SingletonClass();
}
return;
}
//销毁单例对象
static void destoryInstance() {
delete m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
return;
}
private:
//构造函数
SingletonClass() {
std::cout << "SingletonClass的单例对象被创建" << std::endl;
}
//析构函数
~SingletonClass() {
std::cout << "SingletonClass的单例对象被销毁" << std::endl;
}
//禁止拷贝
SingletonClass(const SingletonClass&) = delete;
SingletonClass operator=(const SingletonClass&) = delete;
static SingletonClass* m_pInstance; //单例对象的声明
};
SingletonClass* SingletonClass::m_pInstance = nullptr; //单例对象的定义
int main() {
//若没有创建单例对象,就尝试获取它,会抛出异常
try {
SingletonClass* obj1 = SingletonClass::getInstance();
}
catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
//创建单例对象
SingletonClass::constructInstance();
//...
//多次调用,获取的都是同一个对象
SingletonClass* obj1 = SingletonClass::getInstance();
SingletonClass* obj2 = SingletonClass::getInstance();
SingletonClass* obj3 = SingletonClass::getInstance();
//...
//最后要手动销毁单例对象
SingletonClass::destoryInstance();
return 0;
}
输出:
this instance is not construct, please construct the instance in main() function first
SingletonClass的单例对象被创建
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
SingletonClass的单例对象被销毁
3. RAII メカニズムを使用してシングルトン オブジェクトの自動解放を実現する
1 と 2 では、プログラマはシングルトン オブジェクトを手動でリサイクルする必要がありますが、実際には、特定の状況下では、RAII メカニズムを使用してシングルトン オブジェクトを自動的に解放できます。シングルトン オブジェクトは実際にはクラスの静的メンバーであるため、クラス内のクラスのオブジェクトのライフ サイクルを通じてシングルトン オブジェクトの存続期間を管理するには、特別な手法を使用する必要があります。
なお、静的オブジェクトのライフサイクルはプログラムのライフサイクルと一致しているため、メイン関数の先頭でオブジェクトが初期化されていれば、プログラム実行時に正常にシングルトンオブジェクトを取得できます。
この方法の欠点は、シングルトン オブジェクトをいつ破棄するかを決定できないことです。
#include <iostream>
#include <exception>
class SingletonClass {
public:
//单例对象的全局访问点
static SingletonClass* getInstance() {
if (m_pInstance == nullptr) {
throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
}
std::cout << "对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//初始化单例对象
static void constructInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new SingletonClass();
static SingletonClass_Helper helper; //利用此对象的生命周期来自动销毁单例对象
}
return;
}
private:
//构造函数
SingletonClass() {
std::cout << "SingletonClass的单例对象被创建" << std::endl;
}
//析构函数
~SingletonClass() {
std::cout << "SingletonClass的单例对象被销毁" << std::endl;
}
//禁止拷贝
SingletonClass(const SingletonClass&) = delete;
SingletonClass operator=(const SingletonClass&) = delete;
static SingletonClass* m_pInstance; //单例对象的声明
//内部类,用于辅助销毁单例对象
class SingletonClass_Helper {
public:
~SingletonClass_Helper() {
delete SingletonClass::m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
}
};
};
SingletonClass* SingletonClass::m_pInstance = nullptr; //单例对象的定义
int main() {
std::cout << "main() start" << std::endl;
{
std::cout << "Scope start" << std::endl;
//创建单例对象
SingletonClass::constructInstance();
//...
//多次调用,获取的都是同一个对象
SingletonClass* obj1 = SingletonClass::getInstance();
SingletonClass* obj2 = SingletonClass::getInstance();
SingletonClass* obj3 = SingletonClass::getInstance();
//...
std::cout << "Scope end" << std::endl;
} //出作用域,static SingletonClass_Helper helper;也不会被销毁,因此单例也不会被销毁
//依然可以获得单例对象
SingletonClass* obj1 = SingletonClass::getInstance();
SingletonClass* obj2 = SingletonClass::getInstance();
SingletonClass* obj3 = SingletonClass::getInstance();
std::cout << "main() end" << std::endl;
return 0;
}
输出:
main() start
Scope start
SingletonClass的单例对象被创建
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
Scope end
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
main() end
SingletonClass的单例对象被销毁
4. C++11 は、静的ローカル オブジェクトによって実装された遅延シングルトン モードを使用します。
遅延シングルトンの場合、グローバル アクセス ポイントが複数のスレッドで同時に呼び出されるが、シングルトン オブジェクトが作成されていない (つまり、最初の呼び出し時) という状況を考慮する必要があります。この場合、シングルトン オブジェクトが 2 回作成される可能性があります。ロジックは次のとおりです。
- スレッド セーフティの問題がある遅延シングルトン コード:
#include <iostream>
#include <thread>
class SingletonClass {
public:
//这样的接口实际是一个懒汉单例
static SingletonClass* getInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new SingletonClass();
}
std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//销毁单例对象
static void destoryInstance() {
delete m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
return;
}
private:
//构造函数
SingletonClass() {
std::cout << "SingletonClass的单例对象被创建" << std::endl;
}
//析构函数
~SingletonClass() {
std::cout << "SingletonClass的单例对象被销毁" << std::endl;
}
//禁止拷贝
SingletonClass(const SingletonClass&) = delete;
SingletonClass operator=(const SingletonClass&) = delete;
static SingletonClass* m_pInstance; //static data member,其本质是global object
};
SingletonClass* SingletonClass::m_pInstance = nullptr;
//线程初始化函数
void func() {
SingletonClass::getInstance();
return;
}
int main() {
//多个线程同时去尝试创建单例对象
std::thread th1(func);
std::thread th2(func);
th1.join();
th2.join();
//最后要手动销毁单例对象
SingletonClass::destoryInstance();
return 0;
}
可能的输出:
SingletonClass的单例对象被创建
单例对象的地址为 000001750F380410
SingletonClass的单例对象被创建
单例对象的地址为 000001750F3802B0
SingletonClass的单例对象被销毁
C++11 以前では、この問題はミューテックスを追加することで解決することが一般的でしたが、当然ながらコードの記述が面倒になりますが、このときのロジックは次のとおりです。
- スレッドの安全性を確保するためにミューテックスを使用する遅延シングルトン コード (ここでは二重ロックの手法を使用しています):
#include <iostream>
#include <thread>
#include <mutex>
class SingletonClass {
public:
//这样的接口实际是一个懒汉单例
static SingletonClass* getInstance() {
if (m_pInstance == nullptr) {
//双重锁定 A
std::unique_lock<std::mutex> my_unique_lock(m_mutex); //自动上锁解锁
if (m_pInstance == nullptr) {
//双重锁定 B
m_pInstance = new SingletonClass();
}
}
std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//销毁单例对象
static void destoryInstance() {
delete m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
return;
}
private:
//构造函数
SingletonClass() {
std::cout << "SingletonClass的单例对象被创建" << std::endl;
}
//析构函数
~SingletonClass() {
std::cout << "SingletonClass的单例对象被销毁" << std::endl;
}
//禁止拷贝
SingletonClass(const SingletonClass&) = delete;
SingletonClass operator=(const SingletonClass&) = delete;
static SingletonClass* m_pInstance; //static data member,其本质是global object
static std::mutex m_mutex; //互斥量
};
SingletonClass* SingletonClass::m_pInstance = nullptr;
std::mutex SingletonClass::m_mutex;
//线程初始化函数
void func() {
SingletonClass::getInstance();
return;
}
int main() {
//多个线程同时去尝试创建单例对象
std::thread th1(func);
std::thread th2(func);
th1.join();
th2.join();
//最后要手动销毁单例对象
SingletonClass::destoryInstance();
return 0;
}
输出:
SingletonClass的单例对象被创建
单例对象的地址为 0000027A7A2502C0
单例对象的地址为 0000027A7A2502C0
SingletonClass的单例对象被销毁
C++11 標準では、複数のスレッドが同じ静的ローカル オブジェクトを同時に初期化しようとした場合、初期化は 1 回だけ行われることが保証されています。ここで保証されているのは静的ローカル オブジェクトであり、静的メンバー変数で
はないことに注意してください。そのため、1、2、および 3 で実装されたシングルトン モードは、オブジェクトの作成時にスレッド セーフではありません (もちろん、1、2、および 3 の設計はすべてハングリー シングルトンであるため、この問題は存在しません)。これは、シングルトン オブジェクトが であるためです。static SingletonClass* m_pInstance;
- C++11 静的ローカル オブジェクトを使用して実装された遅延シングルトン コード:
#include <iostream>
#include <thread>
class SingletonClass {
public:
//这样的接口实际是一个懒汉单例
static SingletonClass& getInstance() {
static SingletonClass instance; //function-local static object
return instance;
}
private:
//构造函数
SingletonClass() {
std::cout << "SingletonClass的单例对象被创建" << std::endl;
}
//析构函数
~SingletonClass() {
std::cout << "SingletonClass的单例对象被销毁" << std::endl;
}
//禁止拷贝
SingletonClass(const SingletonClass&) = delete;
SingletonClass operator=(const SingletonClass&) = delete;
};
//线程初始化函数
void func() {
SingletonClass::getInstance();
return;
}
int main() {
//多个线程同时去尝试创建单例对象
std::thread th1(func);
std::thread th2(func);
th1.join();
th2.join();
return 0;
}
输出:
SingletonClass的单例对象被创建
SingletonClass的单例对象被销毁
レイジー シングルトンをハングリー シングルトンに変更することも非常に簡単です。メイン関数で必ずシングルトン オブジェクトを 1 回取得するだけです。
1、2、3、4 の実装の概要
C++11 標準以降は 4 の実装が一般的であり、通常は 4 の方法で使用できます。
しかし、2 の実装も非常に優れており、インターフェイスのデザインは明確で、ハングリーマンモードは推奨される使用方法です。
5. テンプレートを使用して、より一般的なハングリーマン シングルトン モードを実装します。
C++11 より前では、テンプレートのパラメーターは固定されていたため、パラメーターが 6 つ未満のコンストラクターにのみ適応するのが一般的でした。
コードは以下のように表示されます:
#include <iostream>
template <typename T> //模板参数应当是一个类类型
class Singleton {
public:
//单例对象的全局访问点
//因为不需要参数了,所以可以设计的很简单
static T* getInstance() {
if (m_pInstance == nullptr) {
//单例对象未创建,说明代码逻辑存在问题
throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
}
std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//创建单例对象 支持0个参数
static void constructInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new T();
}
return;
}
//创建单例对象 支持1个参数
template <typename T0>
static void constructInstance(T0 arg0) {
if (m_pInstance == nullptr) {
m_pInstance = new T(arg0);
}
return;
}
//创建单例对象 支持2个参数
template <typename T0, typename T1>
static void constructInstance(T0 arg0, T1 arg1) {
if (m_pInstance == nullptr) {
m_pInstance = new T(arg0, arg1);
}
return;
}
//创建单例对象 支持3个参数
template <typename T0, typename T1, typename T2>
static void constructInstance(T0 arg0, T1 arg1, T2 arg2) {
if (m_pInstance == nullptr) {
m_pInstance = new T(arg0, arg1, arg2);
}
return;
}
//创建单例对象 支持4个参数
template <typename T0, typename T1, typename T2, typename T3>
static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
if (m_pInstance == nullptr) {
m_pInstance = new T(arg0, arg1, arg2, arg3);
}
return;
}
//创建单例对象 支持5个参数
template <typename T0, typename T1, typename T2, typename T3, typename T4>
static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
if (m_pInstance == nullptr) {
m_pInstance = new T(arg0, arg1, arg2, arg3, arg4);
}
return;
}
//创建单例对象 支持6个参数
template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
if (m_pInstance == nullptr) {
m_pInstance = new T(arg0, arg1, arg2, arg3, arg4, arg5);
}
return;
}
//销毁单例对象
static void destoryInstance() {
delete m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
return;
}
private:
Singleton() = default;
~Singleton() = default;
//禁止拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static T* m_pInstance;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
//测试部分
//类A,构造函数无参
struct A {
A() {
std::cout << "A类对象被创建" << std::endl;
}
~A() {
std::cout << "A类对象被销毁" << std::endl;
}
};
//类B,构造函数有参数
struct B {
B(int x, int y, int z) {
std::cout << "B类对象被创建" << std::endl;
}
~B() {
std::cout << "B类对象被销毁" << std::endl;
}
};
int main() {
//创建单例对象
Singleton<A>::constructInstance(); //创建A类对象单例
Singleton<B>::constructInstance(1, 2, 3); //创建B类对象单例
//获取单例对象
auto obj_a1 = Singleton<A>::getInstance();
auto obj_a2 = Singleton<A>::getInstance();
auto obj_b1 = Singleton<B>::getInstance();
auto obj_b2 = Singleton<B>::getInstance();
//销毁单例对象
Singleton<A>::destoryInstance();
Singleton<B>::destoryInstance();
return 0;
}
输出:
A类对象被创建
B类对象被创建
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D0400
单例对象的地址为 000001BA054D0400
A类对象被销毁
B类对象被销毁
C++11 以降では、C++11 が導入され可变长模板、万能引用、完美转发
、これらのテクノロジを使用して、非常に一般的なテンプレートを作成できるようになりました。
コードは以下のように表示されます:
#include <iostream>
template <typename T> //模板参数应当是一个类类型
class Singleton {
public:
//单例对象的全局访问点
//因为不需要参数了,所以可以设计的很简单
static T* getInstance() {
if (m_pInstance == nullptr) {
//单例对象未创建,说明代码逻辑存在问题
throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
}
std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
return m_pInstance;
}
//创建单例对象 可以传入任意参数
template <typename... Args>
static void constructInstance(Args&&... args) {
//Args&&... args是万能引用
if (m_pInstance == nullptr) {
m_pInstance = new T(std::forward<Args>(args)...); //完美转发
}
return;
}
//销毁单例对象
static void destoryInstance() {
delete m_pInstance; //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
m_pInstance = nullptr;
return;
}
private:
Singleton() = default;
~Singleton() = default;
//禁止拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static T* m_pInstance;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
//测试部分
//类A,构造函数无参
struct A {
A() {
std::cout << "A类对象被创建" << std::endl;
}
~A() {
std::cout << "A类对象被销毁" << std::endl;
}
};
//类B,构造函数有参数
struct B {
B(int x, int y, int z) {
std::cout << "B类对象被创建" << std::endl;
}
~B() {
std::cout << "B类对象被销毁" << std::endl;
}
};
//类C,有带左值和带右值的构造函数
struct C {
C(const std::string&) {
std::cout << "C类对象被创建,参数为左值" << std::endl;
}
C(std::string&&) {
std::cout << "C类对象被创建,参数为右值" << std::endl;
}
~C() {
std::cout << "C类对象被销毁" << std::endl;
}
};
int main() {
//创建单例对象
Singleton<A>::constructInstance(); //创建A类对象单例
Singleton<B>::constructInstance(1, 2, 3); //创建B类对象单例
std::string c_arg = "hello";
Singleton<C>::constructInstance(std::move(c_arg)); //创建C类对象单例
//获取单例对象
auto obj_a1 = Singleton<A>::getInstance();
auto obj_a2 = Singleton<A>::getInstance();
auto obj_b1 = Singleton<B>::getInstance();
auto obj_b2 = Singleton<B>::getInstance();
auto obj_c1 = Singleton<C>::getInstance();
auto obj_c2 = Singleton<C>::getInstance();
//销毁单例对象
Singleton<A>::destoryInstance();
Singleton<B>::destoryInstance();
Singleton<C>::destoryInstance();
return 0;
}
输出:
A类对象被创建
B类对象被创建
C类对象被创建,参数为右值
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E0380
单例对象的地址为 0000019BE71E0380
A类对象被销毁
B类对象被销毁
C类对象被销毁