C++单例模式及其实现(非常详细)
单例模式的主要目的是确保一个类只能创建一个实例,并提供一个全局访问点以获取该实例。这种模式通常用于需要全局唯一的对象,例如配置文件管理器、日志记录器等。
在单例模式中类的构造函数被私有化,以防止外部直接创建实例,而通过一个静态方法获取该类的唯一实例。在获取实例时如果实例不存在,则创建一个新实例并返回;如果实例已存在,则直接返回已有的实例。这种方式可以确保在整个程序中只有一个实例存在。
单例模式有两种类型,即懒汉式和饿汉式。懒汉式是在第 1 次使用时构建对象的方式,饿汉式是在类加载时就创建对象。

图 1 传统单例模式UML简图
程序在开发过程中将单例模式的实现和业务的实现合并在一起。代码的通用性、安全性和可维护性因人而异,又平白增加了编程工作。
使用传统方式实现通常是一个对象在内部封装一个单例模式,这种实现方式和业务高度耦合。造成针对不同的单例需求都需要进行类似的重复编码,代码的通用性比较差,特别是单例模式主要的差异在于参数的数量和类型上,这样的编码工作是对开发时间的严重浪费。
在 singleton 类中的静态 instance 用于获取实例对象或者创建实例对象,如图 1 所示。在实际开发中通常将单例模式的代码和具体的对象在一起编码。单例模式本身和业务对象是一种强耦合的关系。
将单例模式的实现独立出来,并提供通用的接口和实现方式,可以避免重复编写单例代码,减少代码冗余,并提高代码的可读性和可维护性。这样的设计也能够统一单例的使用方式,方便团队成员之间进行协作,并且代码可以复用。
create() 函数和 get() 函数分别用于创建对象和获取对象,如下图所示:

图 2 C++11模板类UML简图
其中 create() 方法会检查是否已经创建了对象,如果创建了对象,则直接返回对象,否则就创建对象,但是 get() 方法仅仅返回对象指针,如果对象没有创建,则返回空指针。
在类的定义中使用了 std::decay<> 和 std::remove_pointer<>,从而可以扩大类的通用性。假定已有类 a,通过 a 定义了数据类型 obj 和常量指针 ptr,可以通过推导 ptr 类型来初始化单例模式,代码如下:
构造函数是 protected 访问修饰符,这样可以防止直接实例化 singleton 类,只能通过 create() 函数创建单例对象。复制构造函数和移动构造函数被删除,确保只有一个实例存在。
create 用于创建单例对象。它接收任意数量的参数,并使用完美转发将这些参数传递给 T 类型的构造函数。首先,检查 pt_obj__ 是否已经被初始化,如果是,则返回现有的对象。如果没有被初始化,则将尝试为新对象分配内存,并使用提供的参数进行初始化。如果内存分配失败,则将捕获 std::bad_alloc 异常并打印错误消息。最后,返回指向单例对象的指针,代码如下:
create() 函数没有针对对象初始化过程进行控制,通过函数对象的方式来控制初始化过程,将 create 修改为如下代码:
这是一个获取单例对象的函数。它简单地返回了指向当前单例对象的指针。如果 pt_obj__ 并不存在,则返回的也是空指针,代码如下:
接下来需要处理 pt_obj__ 的实例化问题,通过宏定义可以很方便地达到这个目的,代码如下:
先定义一个 Demo 类,它继承自 singleton 模板类。通过 create() 函数创建 Demo 对象时会检查是否已经存在一个单例对象。如果没有,则创建一个新的单例对象,否则返回已经存在的对象的指针。同时,利用 IMP_SINGLETON 宏来实现一个静态的 singleton<Demo> 智能指针变量,以实现 Demo 类的单例模式,代码如下:
如果要实例化单例对象,则需要调用 create() 函数来完成,代码如下:
在单例模式中类的构造函数被私有化,以防止外部直接创建实例,而通过一个静态方法获取该类的唯一实例。在获取实例时如果实例不存在,则创建一个新实例并返回;如果实例已存在,则直接返回已有的实例。这种方式可以确保在整个程序中只有一个实例存在。
单例模式有两种类型,即懒汉式和饿汉式。懒汉式是在第 1 次使用时构建对象的方式,饿汉式是在类加载时就创建对象。
单例模式传统结构
在传统的设计模式资料中单例模式通常采用下图的形式描述结构:
图 1 传统单例模式UML简图
程序在开发过程中将单例模式的实现和业务的实现合并在一起。代码的通用性、安全性和可维护性因人而异,又平白增加了编程工作。
使用传统方式实现通常是一个对象在内部封装一个单例模式,这种实现方式和业务高度耦合。造成针对不同的单例需求都需要进行类似的重复编码,代码的通用性比较差,特别是单例模式主要的差异在于参数的数量和类型上,这样的编码工作是对开发时间的严重浪费。
在 singleton 类中的静态 instance 用于获取实例对象或者创建实例对象,如图 1 所示。在实际开发中通常将单例模式的代码和具体的对象在一起编码。单例模式本身和业务对象是一种强耦合的关系。
C++11模板实现单例模式结构
本节实现的单例模式实际上是懒汉式的一个实现。利用 C++11 的强大表述能力和元编程技巧在编译器层面进行类型检查,将单例模式的实现独立出来,并提供通用的接口和实现方式。这样做可以避免编写单例代码时执行重复性工作,统一接口,方便独立维护,并提高代码质量,单例模式的实现和实际的业务代码相互解耦并独立维护,能够减少开发的重复工作量。将单例模式的实现独立出来,并提供通用的接口和实现方式,可以避免重复编写单例代码,减少代码冗余,并提高代码的可读性和可维护性。这样的设计也能够统一单例的使用方式,方便团队成员之间进行协作,并且代码可以复用。
create() 函数和 get() 函数分别用于创建对象和获取对象,如下图所示:

图 2 C++11模板类UML简图
其中 create() 方法会检查是否已经创建了对象,如果创建了对象,则直接返回对象,否则就创建对象,但是 get() 方法仅仅返回对象指针,如果对象没有创建,则返回空指针。
C++单例模式实现和解析
接下来采用模板类作为单例模式的实现模式。模板参数作为实际要创建的对象类型。使用了 std::unique<> 智能指针类型存储对象指针,同时这个指针也是以静态类型保存的,从而保证了整个进程中只有一个实例,代码如下://designM/singleton.hpp template< typename typeName > class singleton { public: using T = typename std::remove_pointer< typename std::decay< typeName >::type >::type static_assert(std::is_class< T >::value,"");这是一个模板声明,定义了一个模板类 singleton。模板参数 typeName 是要进行单例化的具体类型,而 T 是通过 std::decay<> 和 std::remove_pointer<> 元函数处理后的简单类类型,使用 static_assert 调用元函数 std::is_class<>::value 进行检查来确保T是一个类类型,如果不满足要求,则在编译过程中就会报出错误信息。
在类的定义中使用了 std::decay<> 和 std::remove_pointer<>,从而可以扩大类的通用性。假定已有类 a,通过 a 定义了数据类型 obj 和常量指针 ptr,可以通过推导 ptr 类型来初始化单例模式,代码如下:
struct a { a(int){std::cout<< param<< std::endl;} }; a obj(10); const a * ptr = a; //通过推导生成单例模式类型 using mysingleton = singleton< decltype(ptr) >;std::decay<> 元函数会移除 const 修饰符,std::remove_pointer<> 元函数会移除指针类型,最后保留 a 的原始类型,用原始类型定义变量。通过这种方式可以很安全地对已有变量进行初始化,大大地方便了编码并提高了代码的安全性,同时因为可以通过变量推导出类型,使新的变量和参考的变量之间的逻辑关系更加清晰,代码如下:
//designM/singleton.hpp class singleton { protected: //实际的对象指针,使用static保证内存中只有一个对象 static std::unique_ptr<T > pt_obj__; protected: singleton(){} public: singleton(const singleton&) = delete; //移除复制构造函数 singleton(singleton&&) = delete; //移除右值引用构造函数静态成员变量 pt_obj__ 是一个指向 T 类型对象的 std::unique_ptr,通过这种方式保证只有一个对象实例。
构造函数是 protected 访问修饰符,这样可以防止直接实例化 singleton 类,只能通过 create() 函数创建单例对象。复制构造函数和移动构造函数被删除,确保只有一个实例存在。
create 用于创建单例对象。它接收任意数量的参数,并使用完美转发将这些参数传递给 T 类型的构造函数。首先,检查 pt_obj__ 是否已经被初始化,如果是,则返回现有的对象。如果没有被初始化,则将尝试为新对象分配内存,并使用提供的参数进行初始化。如果内存分配失败,则将捕获 std::bad_alloc 异常并打印错误消息。最后,返回指向单例对象的指针,代码如下:
//designM/singleton.hpp template<typename...Args > static T* create(Args&&...args) { //如果对象存在,则再执行创建操作返回的已有对象 if(pt_obj__)return pt_obj__.get(); try{ pt_obj__.reset(new T(std::forward<Args >(args)...)); }catch(std::bad_alloc&e){ std::cerr<<e.what()<< std::endl; } T * ret = pt_obj__.get(); return ret; }
create() 函数没有针对对象初始化过程进行控制,通过函数对象的方式来控制初始化过程,将 create 修改为如下代码:
template<typename...Args > static T* create(std::function< void(T * ,Args&&...) > func,Args&&...args) { if(pt_obj__)return pt_obj__.get(); //如果对象存在,则再执行创建操作返回的已有对象 try{ pt_obj__.reset(new T(std::forward<Args >(args)...)); //生成对象后执行回调函数,在回调函数中可以针对对象进行额外处理 func(pt_obj__.get(),std::forward<Args >(args)...); }catch(std::bad_alloc&e){ std::cerr<<e.what()<< std::endl; } T * ret = pt_obj__.get(); return ret; }
这是一个获取单例对象的函数。它简单地返回了指向当前单例对象的指针。如果 pt_obj__ 并不存在,则返回的也是空指针,代码如下:
static T * get() { return pt_obj__.get(); }
接下来需要处理 pt_obj__ 的实例化问题,通过宏定义可以很方便地达到这个目的,代码如下:
#define IMP_SINGLETON(type)template< > std::unique_ptr<type > singleton<type >::pt_obj__ = {}它为特定类型 type 实例化了 std::unique_ptr 对象,并将其赋值给 pt_obj__。这个宏必须在业务代码实现的开头中使用,否则在连接过程会报出错误。
C++单例模式应用示例
在下面的例子中,利用 CRTP 的方式实现一个使用示例。这是一种在 C++ 模板编程中的一贯用法,把派生类作为基类的模板参数。通过在派生类中继承模板基类,实现了静态多态性。CRTP 模式允许派生类从模板基类中继承静态成员函数和类型,并且可以利用派生类的具体类型来定制基类的行为。先定义一个 Demo 类,它继承自 singleton 模板类。通过 create() 函数创建 Demo 对象时会检查是否已经存在一个单例对象。如果没有,则创建一个新的单例对象,否则返回已经存在的对象的指针。同时,利用 IMP_SINGLETON 宏来实现一个静态的 singleton<Demo> 智能指针变量,以实现 Demo 类的单例模式,代码如下:
//singleton.cpp //定义一个Demo类,继承自singleton类 class Demo:public singleton<Demo > { public: Demo(int x):data(x){} void print()const { std::cout<< "Demo object created with data = "<< data<< std::endl; } int data; };利用 IMP_SINGLETON 宏,特化 singleton 类模板,以实现 Demo 类的单例模式。IMP_SINGLETON 在全局范围内定义了一个变量智能指针,但并没有对定义的智能指针分配内存和实例化单例对象。
如果要实例化单例对象,则需要调用 create() 函数来完成,代码如下:
IMP_SINGLETON(Demo)在下面的代码中 Demo::create() 方法实现对单例对象的实例化操作。create() 函数是一个工厂函数,在单例模式的模板类中定义并实现,因为 Demo 类继承了单例模式,所以这里使用 Demo 类作用域来调用 create() 函数进行实例化,代码如下:
//singleton.cpp int main() { try{ //创建Demo单例对象 Demo * demo = Demo::create(42); demo->print(); }catch(const std::exception&e){ std::cerr<< e.what()<< std::endl; return 1; } return 0; }