单例多是最经常使用的简单的一种设计模式,实现方法多样,根据不一样的需求有不一样的写法; 同时单例也有其局限性,所以有不少人是反对使用单例的。本文对C++ 单例的常见写法进行了一个总结, 包括懒汉式、线程安全、单例模板等; 按照从简单到复杂,最终回归简单的的方式按部就班地介绍,而且对各类实现方法的局限进行了简单的阐述,大量用到了C++ 11的特性如智能指针, magic static,线程锁; 从头至尾理解下来,对于学习和巩固C++语言特性仍是颇有帮助的。本文的所有代码在 g++ 5.4.0 编译器下编译运行经过,能够在个人github 仓库中找到。ios
单例 Singleton 是设计模式的一种,其特色是只提供惟一一个类的实例,具备全局变量的特色,在任何位置均可以经过接口获取到那个惟一实例;
具体运用场景如:git
懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用get_instance() 方法的时候才 new 一个单例的对象, 若是不被调用就不会占用内存。程序员
1 #include <iostream> 2 // version1: 3 // with problems below: 4 // 1. thread is not safe 5 // 2. memory leak 6 7 class Singleton{ 8 private: 9 Singleton(){ 10 std::cout<<"constructor called!"<<std::endl; 11 } 12 Singleton(Singleton&)=delete; 13 Singleton& operator=(const Singleton&)=delete; 14 static Singleton* m_instance_ptr; 15 public: 16 ~Singleton(){ 17 std::cout<<"destructor called!"<<std::endl; 18 } 19 static Singleton* get_instance(){ 20 if(m_instance_ptr==nullptr){ 21 m_instance_ptr = new Singleton; 22 } 23 return m_instance_ptr; 24 } 25 void use() const { std::cout << "in use" << std::endl; } 26 }; 27 28 Singleton* Singleton::m_instance_ptr = nullptr; 29 30 int main(){ 31 Singleton* instance = Singleton::get_instance(); 32 Singleton* instance_2 = Singleton::get_instance(); 33 return 0; 34 }
运行的结果是github
constructor called!
能够看到,获取了两次类的实例,却只有一次类的构造函数被调用,代表只生成了惟一实例,这是个最基础版本的单例实现,他有哪些问题呢?设计模式
m_instance_ptr
是空的,因而开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance_ptr
仍是空的,因而也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁所以,这里提供一个改进的,线程安全的、使用智能指针的实现;缓存
1 #include <iostream> 2 #include <memory> // shared_ptr 3 #include <mutex> // mutex 4 5 // version 2: 6 // with problems below fixed: 7 // 1. thread is safe now 8 // 2. memory doesn't leak 9 10 class Singleton{ 11 public: 12 typedef std::shared_ptr<Singleton> Ptr; 13 ~Singleton(){ 14 std::cout<<"destructor called!"<<std::endl; 15 } 16 Singleton(Singleton&)=delete; 17 Singleton& operator=(const Singleton&)=delete; 18 static Ptr get_instance(){ 19 20 // "double checked lock" 21 if(m_instance_ptr==nullptr){ 22 std::lock_guard<std::mutex> lk(m_mutex); 23 if(m_instance_ptr == nullptr){ 24 m_instance_ptr = std::shared_ptr<Singleton>(new Singleton); 25 } 26 } 27 return m_instance_ptr; 28 } 29 30 31 private: 32 Singleton(){ 33 std::cout<<"constructor called!"<<std::endl; 34 } 35 static Ptr m_instance_ptr; 36 static std::mutex m_mutex; 37 }; 38 39 // initialization static variables out of class 40 Singleton::Ptr Singleton::m_instance_ptr = nullptr; 41 std::mutex Singleton::m_mutex; 42 43 int main(){ 44 Singleton::Ptr instance = Singleton::get_instance(); 45 Singleton::Ptr instance2 = Singleton::get_instance(); 46 return 0; 47 }
运行结果以下,发现确实只构造了一次实例,而且发生了析构。安全
1 constructor called! 2 destructor called!
shared_ptr和mutex都是C++11的标准,以上这种方法的优势是数据结构
不足之处在于: 使用智能指针会要求用户也得使用智能指针,非必要不该该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实现上咱们但愿越简单越好。多线程
还有更加严重的问题,在某些平台(与编译器和指令集架构有关),双检锁会失效!具体能够看这篇文章,解释了为何会发生这样的事情。架构
所以这里还有第三种的基于 Magic Staic的方法达到线程安全
1 #include <iostream> 2 3 class Singleton 4 { 5 public: 6 ~Singleton(){ 7 std::cout<<"destructor called!"<<std::endl; 8 } 9 Singleton(const Singleton&)=delete; 10 Singleton& operator=(const Singleton&)=delete; 11 static Singleton& get_instance(){ 12 static Singleton instance; 13 return instance; 14 15 } 16 private: 17 Singleton(){ 18 std::cout<<"constructor called!"<<std::endl; 19 } 20 }; 21 22 int main(int argc, char *argv[]) 23 { 24 Singleton& instance_1 = Singleton::get_instance(); 25 Singleton& instance_2 = Singleton::get_instance(); 26 return 0; 27 }
运行结果
1 constructor called! 2 destructor called!
这种方法又叫作 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的做者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
若是当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候必定是初始化过的,因此具备线程安全性。
C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
这是最推荐的一种单例实现方式:
Single&
才能获取对象。另外网上有人的实现返回指针而不是返回引用
1 static Singleton* get_instance(){ 2 static Singleton instance; 3 return &instance; 4 }
这样作并很差,理由主要是没法避免用户使用delete instance
致使对象被提早销毁。仍是建议你们使用返回引用的方式。
有人在网上提供了这样一种单例的实现方式;
1 #include <iostream> 2 3 class A 4 { 5 public: 6 A() { 7 std::cout<<"constructor" <<std::endl; 8 } 9 ~A(){ 10 std::cout<<"destructor"<<std::endl; 11 } 12 }; 13 14 15 A& ret_singleton(){ 16 static A instance; 17 return instance; 18 } 19 20 int main(int argc, char *argv[]) 21 { 22 A& instance_1 = ret_singleton(); 23 A& instance_2 = ret_singleton(); 24 return 0; 25 }
严格来讲,这不属于单例了,由于类A只是个寻常的类,能够被定义出多个实例,可是亮点在于提供了ret_singleton
的方法,能够返回一个全局(静态)变量,起到相似单例的效果,这要求用户必须保证想要获取 全局变量A ,只经过ret_singleton()的方法。
以上是各类方法实现单例的代码和说明,解释了各类技术实现的初衷和缘由。这里会比较推荐 C++11 标准下的 2.2.3 的方式,即使用static local的方法,简单的理由来讲是由于其足够简单却知足全部需求和顾虑。
在某些状况下,咱们系统中可能有多个单例,若是都按照这种方式的话,其实是一种重复,有没有什么方法能够只实现一次单例而可以复用其代码从而实现多个单例呢? 很天然的咱们会考虑使用模板技术或者继承的方法,
在个人博客中有介绍过如何使用单例的模板。
代码示例以下:
1 // brief: a singleton base class offering an easy way to create singleton 2 #include <iostream> 3 4 template<typename T> 5 class Singleton{ 6 public: 7 static T& get_instance(){ 8 static T instance; 9 return instance; 10 } 11 virtual ~Singleton(){ 12 std::cout<<"destructor called!"<<std::endl; 13 } 14 Singleton(const Singleton&)=delete; 15 Singleton& operator =(const Singleton&)=delete; 16 protected: 17 Singleton(){ 18 std::cout<<"constructor called!"<<std::endl; 19 } 20 21 }; 22 /********************************************/ 23 // Example: 24 // 1.friend class declaration is requiered! 25 // 2.constructor should be private 26 27 28 class DerivedSingle:public Singleton<DerivedSingle>{ 29 // !!!! attention!!! 30 // needs to be friend in order to 31 // access the private constructor/destructor 32 friend class Singleton<DerivedSingle>; 33 public: 34 DerivedSingle(const DerivedSingle&)=delete; 35 DerivedSingle& operator =(const DerivedSingle&)= delete; 36 private: 37 DerivedSingle()=default; 38 }; 39 40 int main(int argc, char* argv[]){ 41 DerivedSingle& instance1 = DerivedSingle::get_instance(); 42 DerivedSingle& instance2 = DerivedSingle::get_instance(); 43 return 0; 44 }
以上实现一个单例的模板基类,使用方法如例子所示意,子类须要将本身做为模板参数T 传递给 Singleton<T>
模板; 同时须要将基类声明为友元,这样才能调用子类的私有构造函数。
基类模板的实现要点是:
在 stackoverflow上, 有大神给出了不须要在子类中声明友元的方法,在这里一并放出;精髓在于使用一个代理类 token,子类构造函数须要传递token类才能构造,可是把 token保护其起来, 而后子类的构造函数就能够是公有的了,这个子类只有 Derived(token)
的这样的构造函数,这样用户就没法本身定义一个类的实例了,起到控制其惟一性的做用。代码以下。
1 // brief: a singleton base class offering an easy way to create singleton 2 #include <iostream> 3 4 template<typename T> 5 class Singleton{ 6 public: 7 static T& get_instance() noexcept(std::is_nothrow_constructible<T>::value){ 8 static T instance{token()}; 9 return instance; 10 } 11 virtual ~Singleton() =default; 12 Singleton(const Singleton&)=delete; 13 Singleton& operator =(const Singleton&)=delete; 14 protected: 15 struct token{}; // helper class 16 Singleton() noexcept=default; 17 }; 18 19 20 /********************************************/ 21 // Example: 22 // constructor should be public because protected `token` control the access 23 24 25 class DerivedSingle:public Singleton<DerivedSingle>{ 26 public: 27 DerivedSingle(token){ 28 std::cout<<"destructor called!"<<std::endl; 29 } 30 31 ~DerivedSingle(){ 32 std::cout<<"constructor called!"<<std::endl; 33 } 34 DerivedSingle(const DerivedSingle&)=delete; 35 DerivedSingle& operator =(const DerivedSingle&)= delete; 36 }; 37 38 int main(int argc, char* argv[]){ 39 DerivedSingle& instance1 = DerivedSingle::get_instance(); 40 DerivedSingle& instance2 = DerivedSingle::get_instance(); 41 return 0; 42 }
在 2.2.4 中提供了一种类型的全局变量的方法,能够把一个通常的类,经过这种方式提供一个相似单例的
全局性效果(可是不能阻止用户本身声明定义这样的类的对象);在这里咱们把这个方法变成一个 template 模板函数,而后就能够获得任何一个类的全局变量。
1 #include <iostream> 2 3 class A 4 { 5 public: 6 A() { 7 std::cout<<"constructor" <<std::endl; 8 } 9 ~A(){ 10 std::cout<<"destructor"<<std::endl; 11 } 12 }; 13 14 template<typename T> 15 T& get_global(){ 16 static T instance; 17 return instance; 18 } 19 20 int main(int argc, char *argv[]) 21 { 22 A& instance_1 = get_global<A>(); 23 A& instance_2 = get_global<A>(); 24 return 0; 25 }
能够看到这种方式确实很是简洁,同时类仍然具备通常类的特色而不受限制,固然也所以失去了单例那么强的约束(禁止赋值、构造和拷贝构造)。
这里把函数命名为 get_global()
是为了强调,这里能够经过这种方式获取获得单例最重要的全局变量特性;可是并非单例的模式。
根据stackoverflow上的一个高票答案 singleton-how-should-it-be-used:
You need to have one and only one object of a type in system
你须要系统中只有惟一一个实例存在的类的全局变量的时候才使用单例。
How to create the best singleton:
- The smaller, the better. I am a minimalist
- Make sure it is thread safe
- Make sure it is never null
- Make sure it is created only once
- Lazy or system initialization? Up to your requirements
- Sometimes the OS or the JVM creates singletons for you (e.g. in Java every class definition is a singleton)
- Provide a destructor or somehow figure out how to dispose resources
- Use little memory
越小越好,越简单越好,线程安全,内存不泄露
固然程序员是分流派的,有些是反对单例的,有些人是反对设计模式的,有些人甚至连面向对象都反对 ????.
反对单例的理由有哪些: