介绍一下C++中的设计模式,包括单例模式、工厂模式等。
单例模式
单例模式实现要点
- 构造函数和析构函数是私有的,不允许外部生成和释放
- 静态成员变量和静态返回单例的成员函数
- 禁用拷贝构造和赋值运算符
具体代码实现
示例一
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
| class Singleton1{ public: static Singleton1 *GetInstance(){ if (_instance == nullptr){ _instance = new Singleton1(); } return _instance; } private: Singleton1(){} ~Singleton1(){ std::cout << "~Singleton1()\n"; } Singleton1(const Singleton1&) = delete; Singleton1& operator=(const Singleton1&) = delete; Singleton1(Singleton1&) = delete; Singleton1& operator=(Singleton1&) = delete;
private: static Singleton1 *_instance; }; Singleton1* Singleton1::_instance = nullptr;
int main() {
Singleton1 *s1 = Singleton1::GetInstance(); return 0; }
|
满足三要点,但为错误的单例实现,因为 Singleton1 不能得到正确的析构,无法调用 `~Singleton1(){}`,从而使得堆上的资源无法得到正确的释放。
示例二
那么为了解决 Singleton1 的问题,新增 atexit() 方法,使得在程序退出时,调用手动的析构函数,实现资源释放。
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
| class Singleton2{ public: static Singleton2 *GetInstance(){ if (_instance == nullptr){ _instance = new Singleton2(); atexit(Destructor); } return _instance; } private: static void Destructor(){ if (nullptr == _instance){ delete _instance; _instance = nullptr; } }
Singleton2(){} ~Singleton2(){ std::cout << "~Singleton2()\n"; } Singleton2(const Singleton2&) = delete; Singleton2& operator=(const Singleton2&) = delete; Singleton2(Singleton2&) = delete; Singleton2& operator=(Singleton2&) = delete;
private: static Singleton2 *_instance; }; Singleton2* Singleton2::_instance = nullptr;
int main() { Singleton2 *s2 = Singleton2::GetInstance(); return 0; }
|
示例三
那么为了实现一个线程安全的单例模式,可以引入互斥锁。
注意:
单例模式中的线程安全,并不是指这个对象是线程安全的,而是
创建和返回这个对象的过程是线程安全的。
单检测实现方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton3{ public: static Singleton3 *GetInstance(){ std::lock_guard<std::mutex> lock(_mutex); if (_instance == nullptr){ _instance = new Singleton3(); atexit(Destructor); } return _instance; } private: ... private: static Singleton3 *_instance; };
|
但是这样加入互斥锁存在问题,即 正常情况下 _instance = new Singleton3();
只会运行一次,大部分时候都是拿到 _instance 然后 return _instance;
。
所以,我们只需要在 _instance = new Singleton3();
时才需要加互斥锁,而读取 _instance 的这种读操作没有必要加入互斥锁。
所以我们进行性能优化和代码改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Singleton3{ public: static Singleton3 *GetInstance(){ if (_instance == nullptr){ std::lock_guard<std::mutex> lock(_mutex); _instance = new Singleton3(); atexit(Destructor); } return _instance; } private: ... private: static Singleton3 *_instance; };
|
但是这样存在一个问题,如果在多核的机器上,两个线程同时运行到 std::lock_guard<std::mutex> lock(_mutex); // 将互斥锁移到判断语句内
时,会出现 _instance 被 new 两次,故而我们在加锁后进行双检测。
优化完整的代码如下:
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 41
| class Singleton3{ public: static Singleton3 *GetInstance(){ if (_instance == nullptr){ std::lock_guard<std::mutex> lock(_mutex); if (_instance == nullptr){ _instance = new Singleton3(); atexit(Destructor); } } return _instance; } private: static void Destructor(){ if (nullptr == _instance){ delete _instance; _instance = nullptr; } }
Singleton3(){} ~Singleton3(){ std::cout << "~Singleton3()\n"; } Singleton3(const Singleton3&) = delete; Singleton3& operator=(const Singleton3&) = delete; Singleton3(Singleton3&) = delete; Singleton3& operator=(Singleton3&) = delete;
private: static Singleton3 *_instance; static std::mutex _mutex; }; Singleton3* Singleton3::_instance = nullptr; std::mutex Singleton3::_mutex
int main() { Singleton3 *s3 = Singleton3::GetInstance(); return 0; }
|
但是这种方式也是有问题的,即 没有考虑到 多线程的情况下 CPU指令重排的问题。
new操作符包含三部分:
- 分配内存
- 调用构造函数
- 返回对象指针
当发生指令重排,如出现1 -> 3 -> 2执行顺序,当进行 3.返回对象指针 时,_instance 不为 nullptr,若此时正好有另一个线程来访问 _instance,就直接 return,也就是这个线程拿到的是没有调用构造函数的对象指针,将出现内存泄漏问题。
示例四
那么为了解决 Singleton3 的问题,可以使用原子操作 和 内存屏障的方式
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 41 42 43 44 45 46 47 48 49
| class Singleton4{ public: static Singleton4 *GetInstance(){ Singleton4* tmp = _instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr){ std::lock_guard<std::mutex> lock(_mutex); tmp = _instance.load(std::memory_order_relaxed);
if (tmp == nullptr){ tmp = new Singleton4(); std::atomic_thread_fence(std::memory_order_release); _instance.store(tmp, std::memory_order_relaxed); atexit(Destructor); } } return tmp; } private: static void Destructor(){ Singleton4* tmp = _instance.load(std::memory_order_relaxed); if (nullptr == tmp){ delete tmp; tmp = nullptr; } }
Singleton4(){} ~Singleton4(){ std::cout << "~Singleton4()\n"; } Singleton4(const Singleton4&) = delete; Singleton4& operator=(const Singleton4&) = delete; Singleton4(Singleton4&) = delete; Singleton4& operator=(Singleton4&) = delete;
private: static Singleton4 *_instance; static std::mutex _mutex; }; Singleton4* Singleton4::_instance = nullptr; std::mutex Singleton4::_mutex
int main() { Singleton4 *s4 = Singleton4::GetInstance(); return 0; }
|
示例五
有没有更加优雅的实现方式呢?有的兄弟有的,
利用C++11静态局部变量的特性,即具备线程安全,且为懒加载(延迟加载),同时只会加载一个对象。
并且由于是静态变量,内存能够随着程序结束而自动回收释放,调用析构函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Singleton5{ public: static Singleton5 *GetInstance(){ static Singleton5 instance; return &instance; } private: Singleton5(){} ~Singleton5(){ std::cout << "~Singleton5()\n"; } Singleton5(const Singleton5&) = delete; Singleton5& operator=(const Singleton5&) = delete; Singleton5(Singleton5&) = delete; Singleton5& operator=(Singleton5&) = delete; };
int main() { Singleton5 *s5 = Singleton5::GetInstance(); return 0; }
|
示例六
觉得还是不够优雅?
那还可以利用类模板,将单例的三要点进行 抽象封装
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
| template<typename T> class Singleton6{ public: static T *GetInstance(){ static T instance; return &instance; } protected: Singleton6(){} virtual ~Singleton6(){ std::cout << "~Singleton6()\n"; } private: Singleton6(const Singleton6&) = delete; Singleton6& operator=(const Singleton6&) = delete; Singleton6(Singleton6&) = delete; Singleton6& operator=(Singleton6&) = delete; };
class DesignPattern : public Singleton6<DesignPattern> { friend class Singleton6<DesignPattern>;
private: DesignPattern(){}; ~DesignPattern(){ std::cout << "~DesignPattern()\n"; } }
int main() { DesignPattern *s6 = DesignPattern::GetInstance(); return 0; }
|