首页 > 编程笔记 > C++笔记 阅读:95

C++单例模式及其实现(非常详细)

单例模式的主要目的是确保一个类只能创建一个实例,并提供一个全局访问点以获取该实例。这种模式通常用于需要全局唯一的对象,例如配置文件管理器、日志记录器等。

在单例模式中类的构造函数被私有化,以防止外部直接创建实例,而通过一个静态方法获取该类的唯一实例。在获取实例时如果实例不存在,则创建一个新实例并返回;如果实例已存在,则直接返回已有的实例。这种方式可以确保在整个程序中只有一个实例存在。

单例模式有两种类型,即懒汉式和饿汉式。懒汉式是在第 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;
} 

相关文章