设计模式基础(8):单例模式&享元模式
包含单例模式&享元模式两种模式中的C++示例代码、面向的问题、图解两种模式核心思想
单例模式
面向的需求
- 保证一个类仅有一个实例,并提供一个该实例的全局访问点。
示例代码
1 2 3 4 5 6 7 8 9 10 11
| class Singleton{ private: Singleton(); Singleton(const Singleton& other); public: static Singleton* getInstance(); static Singleton* m_instance; };
Singleton* Singleton::m_instance=nullptr;
|
1 2 3 4 5 6 7
| Singleton* Singleton::getInstance() { if (m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }
|
1 2 3 4 5 6 7 8
| Singleton* Singleton::getInstance() { Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }
|
1 2 3 4 5 6 7 8 9 10 11
| Singleton* Singleton::getInstance() { if(m_instance==nullptr){ Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } } return m_instance; }
|
- reorder不安全
- 这个是指通常意义上,采用new来获得一个对象,按顺序为以下三步(申请内存→调用构造函数得到对象→返回该对象地址(指针))。此时双检查锁是完全没有问题的,但是,编译器可能会为了效率进行优化,采用新的顺序(申请内存→返回该对象地址(指针)→调用构造函数得到对象)。由于CPU是在汇编指令层面进行时间轮片,就有可能,指针地址得到了,但是没有实际对象,从而引发问题。这个称之为reorder不安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| std::atomic<Singleton*> Singleton::m_instance; std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release); m_instance.store(tmp, std::memory_order_relaxed); } } return tmp; }
|
代码思想分析
可以看到在版本的逐个迭代中,最终是得到了多线程下相对完美的解决方法。同时需要指出的是,在c#和Java语言中,直接采用volatile关键字便可以避免reorder不安全。
关键点
- static关键字的使用。
- private下设计构造函数,拷贝构造函数。
- static修饰下,必须类外实现。
- 线程安全问题&reorder安全问题
享元模式
面向的需求
- 系统中存在大量的细粒度的对象,简单的面向对象设计思路会带来很高内存方面的运行代价。
示例代码
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 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Font { private:
string key; public: Font(const string& key){ } }; ß
class FontFactory{ private: map<string,Font* > fontPool; public: Font* GetFont(const string& key){
map<string,Font*>::iterator item=fontPool.find(key); if(item!=footPool.end()){ return fontPool[key]; } else{ Font* font = new Font(key); fontPool[key]= font; return font; }
} void clear(){ } };
|
代码思想分析

先请求获得一个享元模式中的对象,收到请求后的享元工厂判断自己的享元池是否已有,若没有则创建,若有则直接返回已有对象。
关键点
- 享元模式最重要的是确定何时采用享元,要合理的评估怎样的数量级下需要使用享元。
- 享元的实现的重点思想:有则返回,无则创建并记录进池。