设计模式(二)

六、单例模式

定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ——《设计模式》GoF

版本一

 1 class Singleton {
 2 public:
 3   static Singleton * GetInstance() {
 4     if (_instance == nullptr) {
 5       _instance = new Singleton();
 6     }
 7   return _instance;
 8   }
 9 private:
10   Singleton() {} //构造
11   ~Singleton() {}
12   Singleton(const Singleton &clone){} //拷⻉构造
13   Singleton& operator=(const Singleton&) {}
14   static Singleton * _instance;
15 };
16 Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
17  

 

版本一中,_instance对象是指针静态对象,存储在全局数据区,指针指向的对象空间是通过new得到的,在堆区。在此版本中,在堆区创建了对象,但是没有手动释放,造成了内存泄漏。

版本二

 1 // 类对象之间 友元
 2 class Singleton {
 3 public:
 4   static Singleton * GetInstance() {
 5     if (_instance == nullptr) {
 6       _instance = new Singleton();
 7       atexit(Destructor);
 8   }
 9   return _instance;
10   }12 private:
13   static void Destructor() {
14     if (nullptr != _instance) { //
15       delete _instance;
16       _instance = nullptr;
17     }
18   }
19   Singleton();//构造
20   ~Singleton() {}
21   Singleton(const Singleton &cpy); //拷⻉构造
22   Singleton& operator=(const Singleton& other) {}
23   static Singleton * _instance;
24 };
25 Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
26 // 还可以使⽤ 内部类,智能指针来解决; 此时还有线程安全问题
27  

版本二使用atexit()函数解决了进程退出后单例中堆内存的释放问题。除了用这种解决方法,我们还可以将new换成智能指针或者内部类的实现了避免手动创建和释放堆内存。但是,在这两个版本中我们都没有考虑多线程临界资源问题。

版本三

 1 #include <mutex>
 2 class Singleton { // 懒汉模式 lazy load
 3 public:
 4   static Singleton * GetInstance() {
 6     if (_instance == nullptr) {
 7       std::lock_guard<std::mutex> lock(_mutex); // 3.2
 8       if (_instance == nullptr) {
 9         _instance = new Singleton();
10         atexit(Destructor);
11       }
12     }
13     return _instance;
14   }
15 private:
16   static void Destructor() {
17     if (nullptr != _instance) {
18       delete _instance;
19       _instance = nullptr;
20     }
21   }
22   Singleton(){} //构造
23   Singleton(const Singleton &cpy){} //拷⻉构造
24   Singleton& operator=(const Singleton&) {}
25   static Singleton * _instance;
26   static std::mutex _mutex;
27 };
28 Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
29 std::mutex Singleton::_mutex; //互斥锁初始化

版本三使用了互斥锁完成了对临界资源的保护,同时,使用双重判断指针是否为空,防止出现在第一次判空后指针被其他线程赋值后本线程再次赋值的重复操作。

对于new操作,一共有三个顺序步骤

  1. 分配内存
  2. 调用构造函数
  3. 返回指针

在多线程环境下,cpu会可能会执行reorder进行优化,即先分配内存,然后就返回指针,最后再调用构造函数。在这种情况下,可能会导致其他线程得到的指针是一个野指针。

版本四

 1 // volitile关键字 在java中有内存屏障的作用,在C++中没有
 2 #include <mutex>
 3 #include <atomic>
 4 class Singleton {
 5 public:
 6   static Singleton * GetInstance() {
 7     Singleton* tmp = _instance.load(std::memory_order_relaxed);
 8     std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障
 9     if (tmp == nullptr) {
10       std::lock_guard<std::mutex> lock(_mutex);
11       tmp = _instance.load(std::memory_order_relaxed);
12       if (tmp == nullptr) {
13         tmp = new Singleton;
14         std::atomic_thread_fence(std::memory_order_release);//释放内存屏障
16         _instance.store(tmp, std::memory_order_relaxed);
17         atexit(Destructor);
18       }
19     }
20     return tmp;
21   }
22 private:
23   static void Destructor() {
24     Singleton* tmp = _instance.load(std::memory_order_relaxed);
25     if (nullptr != tmp) {
26       delete tmp;
27     }
28   }
29   Singleton(){}
30   Singleton(const Singleton&) {}
31   Singleton& operator=(const Singleton&) {}
32   static std::atomic<Singleton*> _instance;
33   static std::mutex _mutex;
34 };
35 std::atomic<Singleton*> Singleton::_instance;//静态成员需要初始化
36 std::mutex Singleton::_mutex; //互斥锁初始化
37 // g++ Singleton.cpp -o singleton -std=c++11

版本四中使用原子库来实现对_instance对象的new过程的内存屏障。

版本五

 1 // c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束。
 2 // c++ effective的作者写的一个单例版本
 3 class Singleton
 4 {
 5 public:
 6     static Singleton& GetInstance() {
 7         static Singleton instance;
 8         return instance;
 9     }
10 private:
11     Singleton(){}
12     ~Singleton() {}
13     Singleton(const Singleton&) {}
14     Singleton& operator=(const Singleton&) {}
15 };
16 // g++ Singleton.cpp -o singleton -std=c++11
17 /*该版本具备的优点:
18 1. 利⽤静态局部变量特性,延迟加载;
19 2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数;
20 3. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作;
21 4. c++11 静态局部变量初始化时,具备线程安全;
22 */      
23    

版本五是一个常用版本,但是,单例对象的类型被写死了。

版本六

 1 template<typename T>
 2 class Singleton {
 3 public:
 4     static T& GetInstance() {
 5         static T instance; // 这⾥要初始化DesignPattern,需要调用DesignPattern构造函数,同时会调⽤⽗类的构造函数。
 6         return instance;
 7     }
 8 protected:
 9     virtual ~Singleton() {}
10     Singleton() {} // protected修饰构造函数,才能让别⼈继承
11     Singleton(const Singleton&) {}
12     Singleton& operator =(const Singleton&) {}
13 };
14 class DesignPattern : public Singleton<DesignPattern> {
15     friend class Singleton<DesignPattern>; // friend 能让Singleton<T> 访问到DesignPattern构造函数
16 public:
17     ~DesignPattern() {}
18 private:
19     DesignPattern() {}
20     DesignPattern(const DesignPattern&) {}
21     DesignPattern& operator=(const DesignPattern&) {}
22 };
23  

版本六将单例对象的类型抽象了出来,使得对象类型可变。

结构图

 

------------------------------------------------------------------------------------------------------------------------

github:https://github.com/illusorycat/Design-patterns.git

 

posted @ 2022-03-03 01:00  幻cat  阅读(22)  评论(0编辑  收藏  举报