单例模式是一种创建型设计模式,它可以确保一个类在整个程序运行过程中只有一个实例,并提供一个全局访问点以获取该实例。
单例模式的核心思想就是:控制对象的实例化,防止创建多个实例,从而节省资源并保证行为一致性。
关键点:
- 单例类:包含单例实例的类,通常将构造函数声明为私有;
- 静态成员变量:用于存储单例实例的静态成员变量;
- 获取实例方法:静态方法,用于获取单例实例;
- 私有构造函数:防止外部直接实例化单例类;
- 线程安全处理:确保在多线程环境下单例实例的创建是安全的。
- 构造函数和析构函数是私有的,不允许外部生成和释放
- 静态成员变量和静态返回单例的成员函数
- 禁用拷贝构造和赋值运算符
单例类主要是通过一个公共的静态方法getinstance接口,用于获取该类的实例,如果实例不存在,则在该方法内部创建实例并返回。
也就是说,单例类的构造方法不让其他人进行修改和使用;并且单例类只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,就只能进行调用,确保了全局只创建一个实例。
基本写法:
下面是一个最基础的实现:
1class Singleton 2{ 3private: 4 Singleton() {} // 私有构造函数 5 ~Singleton() {} // 私有析构函数 6 Singleton(const Singleton&) = delete; // 禁用拷贝构造 7 Singleton& operator = (const Singleton&) = delete; // 禁用赋值操作符 8 9public: 10 static Singleton* getInstance() { 11 static Singleton instance; 12 return &instance; 13 } 14 15 void show() { 16 cout << "Singleton Show" << endl; 17}; 18 19// 静态成员初始化 20Singleton* Singleton::instance = nullptr; 21 22int main() { 23 Singleton::getInstance()->show(); 24 25 return 0; 26} 27
其实他有两种方式懒汉模式和饿汉模式
懒汉模式
其核心是延迟初始化,可以理解为它很懒,所以它一直没有初始化,只有在首次调用getInstance()时才创建单例实例。
- 优点:节省资源,单例对象未被使用,则不会创建。
- 缺点:需要考虑线程安全的问题,多线程下可能会重复创建。
饿汉模式
其核心是提前初始化,即在程序启动时直接创建单例实例,无论是否被使用。
- 优点:线程安全,实例在程序启动时初始化,避免多线程竞争。
- 缺点:浪费资源,即使没有使用单例对象,也会占用内存。
实际开发中建议使用C++11的局部静态变量懒汉模式
实现过程分析
下面是针对懒汉模式的一个实现过程的分析
示例1
1class Singleton1 { 2public: 3 // 要点2 4 static Singleton1 * GetInstance() { 5 if(_instance == nullptr) { 6 _instance = new Singleton1(); 7 } 8 return _instance; 9 } 10private: 11 // 要点1 12 Singleton1() {} 13 ~Singleton1() { 14 std::cout << "~Singleton1()\n"; 15 } 16 // 要点3 17 Singleton1(const Singleton1 &) = delete; 18 Singleton1& operator = (const Singleton1&) = delete; 19 Singleton1(Singleton1 &&) = delete; 20 Singleton1& operator = (Singleton1 &&) = delete; 21 // 要点2 22 static Singleton1 *_instance; 23}; 24Singleton1* Singleton1::_instance = nullptr; 25
错误的点:_instance = new Singleton1();堆上的资源不能得到正确的析构,资源还是可以释放,但是在释放的时候是无法调用析构函数的。
示例2
1class Singleton2 { 2public: 3 static Singleton2 * GetInstance() { 4 if(_instance == nullpte) { 5 _instance = new Singleton2(); 6 atexit(Destructor); 7 } 8 return _instance; 9 } 10private: 11 static void Destructor() { 12 if(nullptr != _instance) { 13 delete _instance; 14 _instance = nullptr; 15 } 16 } 17 Singleton2() {} 18 ~Singleton2() { 19 std::cout << "~Singleton2()\n"; 20 } 21 Singleton2(const Singleton2 &) = delete; 22 Singleton2& operator = (const Singleton2&) = delete; 23 Singleton2(Singleton2 &&) = delete; 24 Singleton2& operator = (Singleton2 &&) = delete; 25 26 static Singleton2 *_instance; 27}; 28Singleton2* Singleton2::_instance = nullptr; 29
相对于示例1,添加了一个atexit()方法,这个方法就是程序退出的时候,它会去调用Destructor函数,就可以在这个函数里面实现一个手动的析构
示例3
1class Singleton3 { 2public: 3 static Singleton3 * GetInstance() { 4 std::lock_guard<std::mutex> lock(_mutex); 5 if(_instance == nullptr) { 6 std::lock_guard<std::mutex> lock(_mutex); 7 if(_instance == nullptr) { 8 _instance = new Singleton3(); 9 // 1. 分配内存 10 // 2. 调用构造函数 11 // 3. 返回对象指针 12 atexit(Destructor); 13 } 14 } 15 return _instance; 16 } 17private: 18 static void Destructor() { 19 if(nullptr != _instance) { 20 delete _instance; 21 _instance = nullptr; 22 } 23 } 24 Singleton3() {} 25 ~Singleton3() { 26 std::cout << "~Singleton3()\n"; 27 } 28 Singleton3(const Singleton3 &) = delete; 29 Singleton3& operator = (const Singleton3&) = delete; 30 Singleton3(Singleton3 &&) = delete; 31 Singleton3& operator = (Singleton3 &&) = delete; 32 33 static Singleton3 *_instance; 34 static std::mutex _mutex; 35}; 36Singleton3* Singleton3::_instance = nullptr; 37std::mutex Singleton3::_mutex; 38
要实现一个线程安全的单例模式,如果只用if外面那个锁,它是单检测的情况下,它总是返回那个_instance是线程安全的,缺点是,new Singleton3()只会有一次,其他情况都是拿到instance然后进行返回。两个if中间加锁是为了防止有两个线程同时进入而导致new了两次。但是也不对,没有考虑到多线程情况下,指令重排的问题。
示例4
1class Singleton4 { 2public: 3 static Singleton4 * GetInstance() { 4 Singleton4* tmp = _instance.load(std::memory_order_relaxed); 5 std::atomic_thread_fence(std::memory_order_acquire); 6 if(tmp == nullptr) { 7 std::lock_guard<std::mutex> lock(_mutex); 8 tmp = _instance.load(std::memory_order_relaxed); 9 if(tmp == nullptr) { 10 tmp = new Singleton4(); 11 std::atomic_thread_fence(std::memory_order_release); 12 _instance.store(tmp, std::memory_order_relaxed); 13 atexit(Destructor); 14 } 15 } 16 return tmp; 17 } 18private: 19 static void Destructor() { 20 Singleton4* tmp = _instance.load(std::memory_order_relaxed); 21 if(nullptr != tmp) { 22 delete tmp; 23 } 24 } 25 Singleton4() {} 26 ~Singleton4() { 27 std::cout << "~Singleton4()\n"; 28 } 29 Singleton4(const Singleton4 &) = delete; 30 Singleton4& operator = (const Singleton4&) = delete; 31 Singleton4(Singleton4 &&) = delete; 32 Singleton4& operator = (Singleton4 &&) = delete; 33 34 static std::atomic<Singleton4*> _instance; 35 static std::mutex _mutex; 36}; 37std::atomic<Singleton4*> Singleton4::_instance; 38std::mutex Singleton4::_mutex; 39
强制限制指令重排。
里面加入了获取屏障acquire fence和释放屏障release fence,也就后续的读/写操作不会重排到屏障前,且能读取其他线程的释放操作结果;释放屏障前面的读/写操作不会重排到屏障后,且保证当前线程的写入对其他线程可见。
示例5
1class Singleton5 { 2public: 3 static Singleton5* GetInstance() { 4 static Singleton5 instance; 5 return &instance; 6 } 7private: 8 Singleton5() {} 9 ~Singleton5() { 10 std::cout << "~Singleton5()\n"; 11 } 12 13 Singleton5(const Singleton5 &) = delete; 14 Singleton5& operator = (const Singleton5&) = delete; 15 Singleton5(Singleton5 &&) = delete; 16 Singleton5& operator = (Singleton5 &&) = delete; 17}; 18
这个版本是最简单的,如果只是让你简单实现一个单例模式,可以直接写这个版本。
示例6
1template<typename T> 2class Singleton { 3public: 4 static T* GetInstance() { 5 static T instance; 6 return &instance; 7 } 8protected: 9 Singleton() {} 10 virtual ~Singleton() { 11 std::cout << "~Singleton()\n"; 12 } 13private: 14 Singleton(const Singleton &) = delete; 15 Singleton& operator = (const Singleton&) = delete; 16 Singleton(Singleton &&) = delete; 17 Singleton& operator = (Singleton &&) = delete; 18}; 19 20class DesignPattern : public Singleton<DesignPattern> { 21 friend class Singleton<DesignPattern>; 22private: 23 DesignPattern() {} 24 ~DesignPattern() { 25 std::cout << "~DesignPattern()\n"; 26 } 27}; 28
使用类模板把三个要点进行封装
总结:
版本1:堆上资源不能正确析构(没有调用析构函数)
版本2:堆上资源能正确析构(调用了析构函数)
版本3:双检查锁,可能造成内存泄露
版本4:线程安全,原子操作+互斥锁+内存屏障
版本5:C++11静态局部变量具备线程安全特性延迟加载内存正确释放
版本6:模板类封装了单例要点
《设计模式——单例模式》 是转载文章,点击查看原文。
