单例模式是设计模式中最简单也很常见的一种,单例模式的写法有很多种,本文对C++中各种单例的写法进行总结。
原始版本的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Singleton { public: static Singleton& getInstance() { return m_instance; }
private: Singleton(); ~Singleton(); Singleton(const Singleton& rhs); Singleton& operator=(const Singleton& rhs);
private: static Singleton m_instance; };
|
特点:
由于在main函数之前初始化,所以没有线程安全的问题,但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元(可理解为cpp文件和其包含的头文件)中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance()方法会返回一个未定义的实例。
注意:
这里getInstance()返回的实例的引用而不是指针,如果返回的是指针可能会有被外部调用者delete掉的隐患,所以这里返回引用会更加保险一些。
懒汉式的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Singleton { public: static Singleton& getInstance() { if(m_ptrInstance == nullptr) { m_ptrInstance = new Singleton(); } return *m_ptrInstance; }
private: Singleton(); ~Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&);
private: static Singleton* m_ptrInstance; };
|
特点:
先判断有没有现成的实例,如果有直接返回,如果没有则生成新实例并把实例的指针保存到私有的静态属性中。直到getInstance()被访问,才会生成实例,这种特性被称为延迟初始化(Lazy initialization),这在一些初始化时消耗较大的情况有很大优势。
Lazy Singleton不是线程安全的,比如现在有线程A和线程B,m_ptrInstance == NULL的判断,那么线程A和B都会创建新实例。单例模式保证生成唯一实例的规则被打破了。
加锁的懒汉式单例模式
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
| class Singleton { public: Singleton& getInstance() { if(m_ptrInstance == nullptr) { mtx.lock(); if(m_ptrInstance == nullptr) { m_ptrInstance = new Singleton(); } mtx.unlock(); } return *m_ptrInstance; }
private: Singleton(); ~Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&);
private: static Singleton* m_ptrInstance; static std::mutex mtx; };
|
特点:
Lazy Singleton的一种线程安全改造是在Instance()中每次判断是否为NULL前加锁,但是加锁是很慢的。而实际上只有第一次实例创建的时候才需要加锁。双检测锁模式被提出来,只需要在第一次初始化的时候加锁,那么在这之前判断一下实例有没有被创建就可以了,所以多在加锁之前多加一层判断,需要判断两次所有叫Double-Checked。
注意:
此中方法接近完美,但是存在问题:指令重排和原子操作。m_ptrInstance = new Singleton();
不是一个原子操作。这个操作可能存在CPU指令重排。
利用局部静态变量的单例模式(Meyers方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; }
private: Singleton(); ~Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); };
|
特点:
static局部变量只在第一次函数调用时生成,很巧妙,而且是线程安全,最优实现。