单例模式(Singleton Pattern)是使用最广泛的设计模式之一,其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。单例模式常用于资源管理,例如日志、线程池。
定义一个单例类大致分为如下三步:
1、私有化它的构造函数,以防止外界创建单例类的对象;(单例类只能提供私有的构造函数,即保证不能随意创建该类的实例)
2、使用类的私有静态指针变量指向类的唯一实例;(因为构造函数是私有的,其他对象不能创建单例类的实例,只能是单例类自己来创建)
3、使用一个公有的静态方法获取该实例。(由于该类的构造函数是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法创建或者获取它本身的静态私有对象并返回)
二、单例模式实现方案(C++编码)
1、单例的两种实现方式
懒汉式:故名思义,懒汉很懒只有饿了才会去找吃的。也就是说,只有在需要使用的时候才会去实例化。
饿汉式:饿了肯定要饥不择食。在单例类定义的时候就进行实例化。
2、懒汉式单例模式
简单的懒汉式单例模式编码如下:
1 // Singleton.h 2 #pragma once 3 4 class Singleton 5 { 6 public: 7 static Singleton *GetInstance(); 8 9 private: 10 Singleton(); 11 Singleton(const Singleton &); 12 Singleton& operator=(const Singleton &); 13 14 static Singleton *s_instance; 15 }; 16 17 // Singleton.cpp 18 #include <iostream> 19 #include "Singleton.h" 20 21 Singleton *Singleton::s_instance = NULL; 22 23 Singleton::Singleton() {} 24 Singleton::Singleton(const Singleton &) {} 25 Singleton& Singleton::operator=(const Singleton &) {} 26 27 Singleton* Singleton::GetInstance() 28 { 29 if(nullptr == s_instance) 30 { 31 s_instance = new Singleton(); 32 } 33 return s_instance; 34 }
(1)默认构造函数是私有的,外部不能进行单例类的实例化;
(2)拷贝构造函数和赋值运算符也是私有的,以禁止拷贝和赋值;
(3)具有一个私有的静态成员指针 s_instance,指向唯一的实例;
(4)提供一个公有的静态成员函数用于返回实例,如果实例为 NULL,则进行实例化。
3、饿汉式单例模式
简单的饿汉式单例模式编码如下:
1 // Singleton.h 2 #pragma once 3 4 class Singleton 5 { 6 public: 7 static Singleton *GetInstance(); 8 9 private: 10 Singleton(); 11 Singleton(const Singleton &); 12 Singleton &operator=(const Singleton &); 13 14 static Singleton *s_instance; 15 }; 16 17 // Singleton.cpp 18 #include <iostream> 19 #include "Singleton.h" 20 21 Singleton *Singleton::s_instance = new Singleton(); 22 23 Singleton::Singleton() {} 24 Singleton::Singleton(const Singleton &) {} 25 Singleton& Singleton::operator=(const Singleton &) {} 26 27 Singleton* Singleton::GetInstance() 28 { 29 return s_instance; 30 }
说明:与懒汉式单例模式不同之处是,饿汉式在全局作用域进行单例类的实例化,并用此实例初始化单例类的静态成员指针 s_instance。
4、线程安全问题
懒汉式:如果有两个线程同时获取单例类的实例,都发现实例不存在,则都会进行实例化,就会产生两个实例都要赋值给 s_instance,这是是非常不安全的,因而考虑为实例化过程加锁。
1 Singleton* Singleton::GetInstance() 2 { 3 lock(); 4 if(nullptr == s_instance) 5 { 6 s_instance = new Singleton(); 7 } 8 unlock(); 9 10 return s_instance; 11 }
但这个获取实例的方法存在性能问题,每次获取实例的时候都要先上锁,然后再解锁,如果有很多线程的话会造成大量线程的阻塞,改进成双检锁后编码如下:
1 Singleton* Singleton::GetInstance() 2 { 3 if(nullptr == s_instance) 4 { 5 lock(); 6 if(nullptr == s_instance) 7 { 8 s_instance = new Singleton(); 9 } 10 unlock(); 11 } 12 return s_instance; 13 }
绝大多数情况下,获取实例时都是直接返回实例,这时候不会涉及到上锁、解锁的问题。只有在没有实例的时候,才会涉及上锁、解锁,这种情况是很少的。这个获取实例的方法对性能几乎无影响。
局部静态初始化方法(或称之为 Meyers' Singleton,C++11标准):
1 class Singleton 2 { 3 public: 4 static Singleton& GetInstance() 5 { 6 static Singleton instance; 7 return instance; 8 } 9 10 private: 11 Singleton() {} 12 Singleton(const Singleton &) {} 13 Singleton& operator=(const Singleton &) {} 14 };
饿汉式:程序运行初期就进行了单例类实例化,不存在上述的线程安全问题。
5、对象释放问题
一般情况下,单例类的实例都是常驻内存的,一直存在于进程的生命周期,因此不需要手动释放。如果的确需要释放实例占用的内存,一定不能在单例类的析构函数中进行delete操作,这样会造成无限循环,可以考虑增加一个 destroy 方法用于释放内存(或使用智能指针),或者在单例类中定义一个内嵌的垃圾回收类,下面给出垃圾回收类的简单实现。
1 // Singleton.h 2 #pragma once 3 4 class Singleton 5 { 6 public: 7 static Singleton *GetInstance(); 8 9 private: 10 class CGarbo 11 { 12 public: 13 ~CGarbo() 14 { 15 if(nullptr != Singleton::s_instance) 16 { 17 delete Singleton::s_instance; 18 } 19 } 20 }; 21 22 static CGarbo s_gb; 23 24 private: 25 Singleton(); 26 Singleton(const Singleton &); 27 Singleton& operator=(const Singleton &); 28 29 private: 30 static Singleton *s_instance; 31 }; 32 33 // Singleton.cpp 34 #include <iostream> 35 #include "Singleton.h" 36 37 Singleton *Singleton::s_instance = NULL; 38 39 Singleton::Singleton() {} 40 Singleton::Singleton(const Singleton &) {} 41 Singleton& Singleton::operator=(const Singleton &) {} 42 43 Singleton *Singleton::GetInstance() 44 { 45 if (nullptr == s_instance) 46 { 47 lock(); 48 if(nullptr == s_instance) 49 { 50 s_instance = new Singleton(); 51 } 52 unlock(); 53 } 54 55 return s_instance; 56 }
三、Reference
C++单例(知乎):
C++单例(博客园):
盘点C++几种常见的设计模式及具体实现: