C++单例模板#
单例的写法基本都相同,定义一个模板方便使用单例模式。
单例模式需要确保资源初始化是线程安全的,因而出现了以下几种方法(选自《C++ 单例模式的模板实现》、《单例模式很简单?但你真能写对吗?》):
1. 直接加锁#
1
2
3
4
5
6
7
8
|
//线程安全版本,但锁的代价过高
Singleton* Singleton::GetInstance() {
Lock lock; //伪代码 加锁
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
|
虽然确保了初始化只有一次,但是每次GetInstance
的时候都会上锁,导致性能不高。
2. 双检查锁#
既然初始化后不需要再加锁,那么上锁之前增加一个判断if (instance == nullptr)
,资源未初始化才上锁并初始化,并且在初始化之前再判断一次资源是否为空,确保如果多个线程同时进入了第一个if
时,只有一个线程会初始化资源。
1
2
3
4
5
6
7
8
9
10
11
|
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::GetInstance() {
//先判断是不是初始化了,如果初始化过,就再也不会使用锁了
if(instance==nullptr){
Lock lock; //伪代码
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
|
看起来很完美,不是吗。但是经过编译器优化后的程序,并不一定按照你想的那样工作。instance = new Singleton();
可以分成三个步骤执行:
- 分配了一个Singleton类型对象所需要的内存。
- 在分配的内存处构造Singleton类型的对象。
- 把分配的内存的地址赋给指针instance。
由于编译器的优化,可能出现内存读写的乱序执行,只能确保步骤1最先执行。假如线程A按1,3,2的顺序执行,在执行3后,instance
就不是nullptr
了,线程B就会直接return instance;
,然而此时对象尚未构造,线程B一旦使用这个对象就会导致bug。
3. 饿汉模式#
上面的两种方式,都是在第一次使用时才分配资源初始化对象,这种方式也叫懒汉模式,就像大明湖的蛤蟆,一戳一蹦跶。那么我们干脆在程序进入主函数之前就初始化,这样就绕开了线程安全的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
template<typename T>
class EagerSingleton
{
private:
static T* t_;
public:
static T& GetInstance()
{
return *t_;
}
EagerSingleton(T&&) = delete;
EagerSingleton(const T&) = delete;
void operator= (const T&) = delete;
protected:
EagerSingleton() = default;
virtual ~EagerSingleton() = default;
};
template<typename T>
T* EagerSingleton<T>::t_ = new (std::nothrow) T;
|
但是这种模式也存在问题,哪怕对象不使用也会被初始化。假如对象的资源开销很大就会造成浪费。
4. C++ 11带来福音#
Scott Meyers 在 《Effective C++》 的 Item 4: Make sure that objects are initialized before they’re used 里面提出了一种利用 C++ 的 static
关键字来实现的单例模式,这种实现非常简洁高效,它的特点是:
- 仅当程序第一次执行到
GetInstance
函数时,执行 instance
对象的初始化;
- 在 C++ 11 之后,被
static
修饰的变量可以保证是线程安全的;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#ifndef SINGLETON_H
#define SINGLETON_H
/**
* \brief 单例模板
* \tparam T 需要作为单例模式使用的类型
*
* \code{.cpp}
* // 直接使用(推荐)
* Singleton<T>::GetInstance()
*
* // 继承派生
* class C : public Singleton<T>
* \endcode
*/
template<typename T, typename... Args>
class Singleton {
public:
// 删除拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符,以防止外部复制、赋值或移动对象
Singleton(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton &operator=(const Singleton &) = delete;
Singleton &operator=(Singleton &&) = delete;
static T &GetInstance(Args &&... args) {
static T instance(std::forward<Args>(args)...);
return instance;
}
protected:
Singleton() = default;
virtual ~Singleton() = default;
};
#endif // SINGLETON_H
|
通过禁用单例类的 copy constructor,move constructor 和 operator= 可以防止类的唯一实例被拷贝或移动;不暴露单例类的 constructor 和 destructor 可以保证单例类不会通过其他途径被实例化,同时将两者定义为 protected 可以让其被子类继承并使用。
5. AI的智慧#
《4. C++ 11带来福音》中的单例模板代码存在一个问题——单例对象存在于栈内存,如果类较大的话无疑是一笔不必要的开销。为此在AI的配合下实现如下的模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
#ifndef SINGLETON_H
#define SINGLETON_H
#include <memory>
#include <mutex>
/**
* \brief 单例模板
* \tparam T 需要作为单例模式使用的类型
*
* \code{.cpp}
* // 直接使用(推荐)
* Singleton<T>::GetInstance()
*
* // 继承派生
* class C : public Singleton<T>
* \endcode
*/
template<typename T, typename... Args>
class Singleton {
public:
// 删除拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符,以防止外部复制、赋值或移动对象
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton &operator=(Singleton &&) = delete;
// 单例模板类的获取实例函数,使用完美转发和std::call_once来创建并返回单例对象
static T *GetInstance(Args &&... args) {
std::call_once(flag_, [&]() { instance_ = std::make_unique<T>(std::forward<Args>(args)...); });
return instance_.get();
}
private:
// 单例模板类的构造函数和析构函数,私有化以防止外部创建或销毁对象
Singleton() = default;
~Singleton() = default;
// 单例模板类的成员变量,包括一个智能指针指向单例对象,和一个once_flag变量保证线程安全
static std::unique_ptr<T> instance_;
static std::once_flag flag_;
};
// 初始化单例模板类的静态成员变量
template<typename T, typename... Args>
std::unique_ptr<T> Singleton<T, Args...>::instance_ = nullptr;
template<typename T, typename... Args>
std::once_flag Singleton<T, Args...>::flag_;
#endif // SINGLETON_H
|