这几天看了下C++中单例模式的实现,发现涉及到的问题实在太多。现将看的总结如下:
单例模式顾名思义就是类只存在一个实例,分为懒汉式和饿汉式两种:懒汉式是在要使用单例的时候才创建对象,延迟了类对象创建的时间,一定程度上节省了空间,但是线程不安全;饿汉式就是在static单例指针定义时就直接创建对象,是线程安全的,因为一般多线程程序都是从单线程开始启动的,将单例的创建过程放在启动时就不会有线程安全问题。
饿汉式:
singleton.h
class Singleton
{
public:
static Singleton *getInstance();
Singleton(Singleton const &)=delete; //将拷贝构造函数和拷贝赋值运算符定义为delete,防止通过拷贝和赋值生成多个实例
void operator=(Singleton const &)=delete;
private:
Singleton() {} //构造函数私有化
static Singleton *pInstance;
};
singleton.cpp
Singleton *Singleton::pInstance=new Singleton;
Singleton *Singleton::getInstance()
{
return pInstance;
}
懒汉式:
singleton.h
class Singleton
{
public:
static Singleton *getInstance();
Singleton(Singleton const &)=delete; //将拷贝构造函数和拷贝赋值运算符定义为delete,防止通过拷贝和赋值生成多个实例
void operator=(Singleton const &)=delete;
private:
Singleton() {} //构造函数私有化
static Singleton *pInstance;
};
singleton.cpp
Singleton *Singleton::pInstance=nullptr;
Singleton *Singleton::getInstance()
{
if(pInstance == nullptr)
pInstance=new Singleton();
return pInstance;
}
注意这里有两个问题:
1、析构函数得不到调用,可能造成内存泄露。
2、只能用在单线程环境。
因为当多个线程访问下面的临界区时,可能创建多个实例对象,这就与单例模式相违背了。
if(pInstance == nullptr)
pInstance=new Singleton();
很显然我们想到要加锁。
Lock lock;
if(pInstance == nullptr)
pInstance=new Singleton();
然而使用锁肯定带来开销,而且程序中每次要使用这个单例时,都要经过加锁、解锁的过程,这显然是不必要的,因为,一旦我们单例建立起来后,条件就不成立,所以大部分的加锁是不必要的。因此给出了下面的双重判断机制。
if(pInstance == nullptr)
{
Lock lock;
if(pInstance == nullptr)
pInstance=new Singleton();
}
这显然避免了单例对象建立起来后需要获取单例对象时所不必要的加锁过程。
但是C++中事情还不是那么简单,考虑到CPU的指令优化,指令执行顺序的重排,上面的双重判断机制也不安全。
pInstance=new Singleton();这条语句可以分成下面三个步骤:
- 给Singleton对象分配内存
- 在分配的内存上构造Singleton对象
- 使pInstance指针指向分配的内存
考虑到指令重排,所以上面的双重判断可以为:
if(pInstance == nullptr)
{
Lock lock;
if(pInstance == nullptr)
{
pInstance = // Step 3
operator new(sizeof(Singleton)); // Step 1
new (pInstance) Singleton; // Step 2
}
}
step 3、1在step 2之前执行,假设线程A通过双重判断执行完Step 3、1,此时线程A被挂起,线程B执行,由于此时pInstance不为nullptr,所以线程B中返回pInstance,但是单例对象还没构造成功,所以线程B中对单例的操作将失败。
我们可以考虑到volatile关键字,防止处理器优化而进行的指令重排,但是C++标准中没有定义volatile在多线程中的保证,所以还是线程不安全。
具体可以参考:http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
一种建立在栈空间上的懒汉式单例模式实现:
C++11中是线程安全的,而且能确保被析构,根据C++标准:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
class S
{
public:
static S& getInstance()
{
static S instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return instance;
}
private:
S() {} // Constructor? (the {} brackets) are needed here.
// C++ 03
// ========
// Don't forget to declare these two. You want to make sure they
// are unacceptable otherwise you may accidentally get copies of
// your singleton appearing.
S(S const&); // Don't Implement
void operator=(S const&); // Don't implement
// C++ 11
// =======
// We can use the better technique of deleting the methods
// we don't want.
public:
S(S const&) = delete;
void operator=(S const&) = delete;
// Note: Scott Meyers mentions in his Effective Modern
// C++ book, that deleted functions should generally
// be public as it results in better error messages
// due to the compilers behavior to check accessibility
// before deleted status
};