赞
踩
在C++的代码中是要尽量避免使用全局变量的,全局变量可能在程序的任一地方被修改,提高代码的定位难度,还会导致代码耦合性变高,难以模块化测试。但有时候一个类对象想要全局使用,且全局只能初始化一次,这时就可以引入单例模式的思想。这里提到的只是单例模式应用的一个场景,实际上全局变量和单例没太大的相关性,单例和静态类的区别在这里不再赘述,网上可以查找到相关的对比,比如下面这个是C#上的说明:
在《Head First 设计模式》一书中,单例模式的定义是“确保一个类只有一个实例,并提供一个全局访问点”,类图如下:
通过类图可以看到单例类有两个明显的特点:① 构造函数是私有的,这样在类外就不能随意进行实例化;② 有一个静态的函数,用来获取类内实例化的对象,类的静态函数可以在代码的任何地方进行调用。根据单例类的这两个特点,可以设计出两种经典的单例模式:懒汉式和饿汉式,还有基于懒汉式的线程安全的双检测锁模式,在下面这篇文章中有很详细的说明:
为了在多线程下使用单例模式,基于线程安全问题仅出现在第一次初始化(new)过程中,引入了双检锁模式。第一次检测不涉及到对象的创建,因此是不加锁的检测,不需要锁的时间消耗,只有检测到对象暂未创建的时候,才会进行对象的加锁操作,然后再进行数据的第二次检测,避免在加锁期间,对象已经创建成功,只有二次检测到对象尚未创建才会创建唯一的实例。
为了在大规模的代码中,方便多个类都定义为单例类,且在写类的代码时不需要太大变动且模式统一,因此本文定义了一个模板类,通过可变模板参数和友元函数的概念,实现调用的统一,具体实现代码如下:
- template <typename T>
- class Singleton {
- public:
- template<typename... Args>
- static inline std::shared_ptr<T> instance(Args&& ... args) {
- // 双检测锁模式
- if (!instance_.get()) {
- std::unique_lock<std::mutex> lock(instanceMutex_);
- if (!instance_.get()) {
- instance_.reset(new T(std::forward<Args>(args)...));
- }
- }
- return instance_;
- }
-
- private:
- Singleton() = default;
- virtual ~Singleton() = default;
- Singleton(const Singleton&) = default;
- Singleton& operator = (const Singleton&) = delete;
-
- // 实例
- static std::shared_ptr<T> instance_;
- static std::mutex instanceMutex_;
- };
-
- template <typename T>
- std::shared_ptr<T> Singleton<T>::instance_;
-
- template <typename T>
- std::mutex Singleton<T>::instanceMutex_;
-
- #define SINGLETON_DECL(type) \
- friend class std::shared_ptr< type >; \
- friend class Singleton< type >;
双检锁模式可以在多线程下保证线程的安全,但并不是绝对的。某些内存模型、编译器的优化或者运行时优化等情况下,会先分配完内存再进行数据的构造,造成另一个线程如果调用getInstance()获取到一个不完全初始化的对象,从而出现崩溃的情况。这种情况一个是通过内存屏障(memory barrier)的方式解决,一个是在主函数或者是比较早期的时候就完成单例的创建,还有一个方式是atomic实现,可以参考第一节中《C++ 单例模式》链接里面讲解的很清晰,这里不再赘述,只提供一种单例模板类的实现思路。
将单例模板类定义在头文件Singleton.h中,其它头文件中想要定义为单例模式的类,只需要对其进行引用(#include Singleton.h)即可进行使用,假设我们想定义一个单例类A,则可以通过以下代码实现:
- class A{
- private:
- A() {}
-
- public:
- ~A() {}
-
- int funA() {}
-
- private:
- SINGLETON_DECL(A);
- };
-
-
- // 调用单例类A的公共成员函数
- Singleton<A>::instance()->funA();
- // 在调用的时候会进行实例是否已创建的检查
可以看到调用是比较简单且统一的,不需要针对每个类再单独写双检测锁的部分。单例模式的实现方式有很多,可以根据项目实际需求选择具体方式,不管是懒汉式、饿汉式还是优化实现,都可以通过可变参数模板进行全局的统一管理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。