超经典单例模板类详解

简介: 超经典单例模板类详解

单例模式,相信大家也并不陌生,而且在项目中也会经常用到,对于频繁使用的类,我们都想要提高它的复用性,今天小豆君就给大家介绍一下如何用类模板来实现一个单例模板类。

友情提示,本篇文章会有点长,如果你真想学到点东西,那就仔细阅读吧,而且有很多细节都是非常棒的编程技巧,相信你会学到不少好东西的。

1 单例类实现

1.1 本地单例类实现方法

//该类将构造函数,拷贝构造函数,赋值构造函数访问权限设置成private,
//这样就屏蔽了直接创建实例的操作,而想要获得该类的实例,只能通过
//instance方法获取
class LocalStaticInstanceInt {
public:
    static LocalStaticInstanceInt& intance()
    {
       //此处声明为局部静态变量,只在第一次调用时创建并初始化
        static LocalStaticInstanceInt instance;
        return instance;
    }
    virtual ~LocalStaticInstanceInt()
    {
        cout << "destory LocalStaticInstanceInt" << endl;
    }
    int  getValue() const{return value;}
    void setValue(int val){value = val;}
private:
    LocalStaticInstanceInt():value(0) //防止实例
    {
        cout << "create LocalStaticInstanceInt" << endl;
    }
    LocalStaticInstanceInt(const LocalStaticInstanceInt&) {} //防止拷贝构造一个实例
    LocalStaticInstanceInt& operator=(const LocalStaticInstanceInt&){return *this;} //防止赋值出另一个实例
private:
    int value;
};

1.2 懒惰初始化单例类实现方法

有的时候,采用单例的指针来访问类方法更方便点

class LazyInstance {
public:
    static LazyInstance* intance()
    {
        static LazyInstance* ins = new LazyInstance();
        return ins;
    }
    virtual ~LazyInstance()
    {
        cout << "destory LazyInstance" << endl;
    }
    int  getValue() const{return value;}
    void setValue(int val){value = val;}
private:
    LazyInstance():value(0)  //防止实例
    {
        cout << "create LazyInstance" << endl;
    }
    LazyInstance(const LazyInstance&) {} //防止拷贝构造一个实例
    LazyInstance& operator=(const LazyInstance&){return *this;} //防止赋值出另一个实例
    int value;
};

因为只有在调用单例方法时才会创建一个实例,所以这种技术叫做懒惰初始化法,当然1.1中的方法也运用了懒惰初始化法。

1.3 测试

int main()
{
    {
        LocalStaticInstanceInt& lsi1 = LocalStaticInstanceInt::intance();
        LocalStaticInstanceInt& lsi2 = LocalStaticInstanceInt::intance();
        cout << "ins1.value = "<< lsi1.getValue() << "  ins2.value = " << lsi2.getValue() << endl;
        //因为lsi1与lsi2是同一个对象的引用,所以当lsi1设置value为5时,lsi2的也相应改变
        lsi1.setValue(5);
        cout << "ins1.value = "<< lsi1.getValue() << "  ins2.value = " << lsi2.getValue() << endl;
    }
    {
        LazyInstance* si1 = LazyInstance::intance();
        LazyInstance* si2 = LazyInstance::intance();
        cout << "ins1.value = "<< si1->getValue() << "  ins2.value = " << si2->getValue() << endl;
        si1->setValue(5);
        cout << "ins1.value = "<< si1->getValue() << "  ins2.value = " << si2->getValue() << endl;
    }
}

以下是输出结果:

细心的同学可能发现,在输出结果中,只释放了LocalStaticInstance的实例,而LazyInstance的实例并未释放,这是因为,LazyInstance的实例是在堆上创建的,而在程序结束时,并没有对该实例进行释放操作,所以就造成了内存泄漏。

1.4使用atexist函数释放实例对象

针对LazyInstance实例没有释放的问题,可以采用atexist注册一个函数,让程序退出时释放LazyInstance实例,当然你也可以做一些其它的收尾操作。

//在main函数之上添加如下函数
void destroyInstance(void)
{
    LazyInstance* si = LazyInstance::intance();
    if (si)
    {
        delete si;
    }
}
//在main函数内最后一行添加调用
atexit(destroyInstance);

再次运行程序

这回LazyInstance已经被释放了。

1.5 更好的办法

使用atexist固然可以解决问题,但每次都需要向destroyInstance中添加删除操作,无疑是麻烦的,而且有的人也会忘记添加。所以又有了下面的对策。

template <class T>
class Destroyer
{
    T* doomed;
public:
    Destroyer(T* q) : doomed(q)
    {
        assert(doomed);
    }
    ~Destroyer();
};
template <class T>
Destroyer<T>::~Destroyer()
{
    try
    {
        if(doomed)
            delete doomed;
    } catch(...) { }
    doomed = 0;
}
class LazyInstance {
public:
    static LazyInstance* intance()
    {
        static LazyInstance* ins = new LazyInstance();
        //新增一个局部静态变量destory,当程序结束时,
        //会调用该变量的析构函数,从而用这个析构函数释放LazyInstance实例
        static Destroyer<LazyInstance> destory(ins);
        return ins;
    }
    virtual ~LazyInstance()
    {
        cout << "destory LazyInstance" << endl;
    }
    int  getValue() const{return value;}
    void setValue(int val){value = val;}
private:
    LazyInstance():value(0)  //防止实例
    {
        cout << "create LazyInstance" << endl;
    }
    LazyInstance(const LazyInstance&) {} //防止拷贝构造一个实例
    LazyInstance& operator=(const LazyInstance&){return *this;} //防止赋值出另一个实例
    int value;
};

在代码中,我定义了一个Destroyer的类模板,并且在instance()接口中新定义了一个Destroyer类型的局部静态变量,并且以LazyInstance的实例指针作为参数,那么当程序结束时,会自动调用Destroyer的析构函数,而在析构函数中,释放了LazyInstance实例。

修改main函数

int main()
{
        LazyInstance* si1 = LazyInstance::intance();
        LazyInstance* si2 = LazyInstance::intance();
        cout << "ins1.value = "<< si1->getValue() << "  ins2.value = " << si2->getValue() << endl;
        si1->setValue(5);
        cout << "ins1.value = "<< si1->getValue() << "  ins2.value = " << si2->getValue() << endl;
}

运行程序

LazyInstance的实例被自动释放。

2 重用单例类

在第1小节中,我们创建了一个带有引用的单例LocalStaticInstanceInt,该单例对一个int型的成员变量进行了get和set操作,假设现在又有一个类需要用到单例模式,那么通常的做法是,再创建一个单例类。

例如以下单例,操作一个string对象:

class LocalStaticInstanceString {
public:
    static LocalStaticInstanceString& intance()
    {
        static LocalStaticInstanceString instance;
        return instance;
    }
    virtual ~LocalStaticInstanceString()
    {
        cout << "destory LocalStaticInstance" << endl;
    }
    string  getValue() const{return value;}
    void setValue(string val){value = val;}
private:
    LocalStaticInstanceString():value(0) //防止实例
    {
        cout << "create LocalStaticInstance" << endl;
    }
    LocalStaticInstanceString(const LocalStaticInstanceString&) {} //防止拷贝构造一个实例
    LocalStaticInstanceString& operator=(const LocalStaticInstanceString&){return *this;} //防止赋值出另一个实例
private:
    string value;
};

LocalStaticInstanceString与LocalStaticInstanceInt长得如此相像,为了提高重用性,那么我们可以采用继承的方法重写这两个类,而基类采用单例类模板

template <class T>
class LocalStaticInstance {
public:
    static T& intance()
    {
        static T sinstance;
        return sinstance;
    }
    virtual ~LocalStaticInstance(){}
//此处使用protected是为了派生类可以继承于它,并调用默认构造函数
//如果是private则无法调用
protected:
    LocalStaticInstance() = default; //防止实例
    LocalStaticInstance(const LocalStaticInstance&) {} //防止拷贝构造一个实例
    LocalStaticInstance& operator=(const LocalStaticInstance&){return *this;} //防止赋值出另一个实例
};
//继承LocalStaticInstance<LocalStaticInstanceInt>类,
//单例的instance的方法就不需要你写了
class LocalStaticInstanceInt: public LocalStaticInstance<LocalStaticInstanceInt>
{
public:
    int getValue() const {return value;}
    void setValue(int val) {value = val;}
    //使用友元,使基类可以调用私有的构造函数
    friend class LocalStaticInstance<LocalStaticInstanceInt>;
private:
    LocalStaticInstanceInt()
    {
        cout << "create LocalStaticInstanceInt" << endl;
    }
    virtual ~LocalStaticInstanceInt()
    {
        cout << "destory LocalStaticInstanceInt" << endl;
    }
private:
    int value;
};
//继承LocalStaticInstance<LocalStaticInstanceString>类
class LocalStaticInstanceString: public LocalStaticInstance<LocalStaticInstanceString>
{
public:
    string getValue() const {return value;}
    void setValue(string val) {value = val;}
    friend class LocalStaticInstance<LocalStaticInstanceString>;
private:
    LocalStaticInstanceString():value("hello")
    {
        cout << "create LocalStaticInstanceString" << endl;
    }
    ~LocalStaticInstanceString()
    {
        cout << "destory LocalStaticInstanceString" << endl;
    }
private:
    string value;
};

下面的main函数展示了如何调用

int main()
{
    {
        LocalStaticInstanceInt& lsi1 = LocalStaticInstance<LocalStaticInstanceInt>::intance();
        LocalStaticInstanceInt& lsi2 = LocalStaticInstance<LocalStaticInstanceInt>::intance();
        cout << "lsi1.value = "<< lsi1.getValue() << "  lsi2.value = " << lsi2.getValue() << endl;
        lsi1.setValue(5);
        cout << "lsi1.value = "<< lsi1.getValue() << "  lsi2.value = " << lsi2.getValue() << endl;
    }
    {
        LocalStaticInstanceString& lss1 = LocalStaticInstanceString::intance();
        LocalStaticInstanceString& lss2 = LocalStaticInstanceString::intance();
        cout << "lss1.value = "<< lss1.getValue() << "  lss2.value = " << lss2.getValue() << endl;
        lss1.setValue("world");
        cout << "lss1.value = "<< lss1.getValue() << "  lss2.value = " << lss2.getValue() << endl;
    }
}

通过上面的分析,使用类模板减少了很多重复的代码,在使用过程中,直接继承LocalStaticInstance的模板类即可,是不是用起来方便多了。

对于LazyInstance(使用指针实现的懒惰初始化单例类)的类模板实现,请大家作为练习。

3 合并多个单例基类类模板

上一小节我们创建了两个单例基类模板LocalStaticInstance和LazyInstance(作为练习) 我们在使用中,如果两种模板都想用,那么,使用起来还是会不太方便。因此,有没有一种方法能够把这两个类合并起来呢,答案当然是有的,我们可以创建一个新模板,然后把这两个类作为参数传入新模板的接口中,到时候如果想使用指针,则可以把LazyInstance类型作为参数传入模板,就像我们使用vector时传入容器大小一样方便。

#include <iostream>
#include <assert.h>
using namespace std;
//使用局部静态变量实现的单例类
//注意这是类,而不是模板,它实现了下面Singleton模板的create方法
class LocalStaticInstance {
protected:
    template <class T>
    static void create(T*& ptr)
    {
        static T instance;
        ptr = &instance;
    }
};
//释放器 用于释放在堆上分配的单例
template <class T>
class Destroyer
{
    T* doomed;
public:
    Destroyer(T* q) : doomed(q)
    {
        assert(doomed);
    }
    ~Destroyer();
};
//在类外实现的析构函数,注意析构函数开头要写成Destroyer<T>形式
//因为Destroyer<T>才是一个真正的类型
template <class T>
Destroyer<T>::~Destroyer()
{
    try
    {
        if(doomed)
            delete doomed;
    } catch(...) { }
    doomed = 0;
}
//使用指针实现,懒惰初始化单例类
class LazyInstance
{
protected:
    template <class T>
    static void create(T*& ptr)
    {
        ptr = new T;
        static Destroyer<T> destroyer(ptr);
    }
};
//Singleton类对外提供一致接口,默认调用LazyInstance
template <class T, class InstancePolicy=LazyInstance>
class Singleton : private InstancePolicy
{
public:
    static T* instance();
};
//在类声明之外定义instance接口
template <class T, class InstancePolicy>
T* Singleton<T, InstancePolicy>::instance()
{
    static T* ptr = 0;
    if(!ptr)
    {
        InstancePolicy::create(ptr);
    }
    return const_cast<T*>(ptr);
}
//使用局部静态单例类,需要显式指定
class LocalStaticInstanceInt: public Singleton<LocalStaticInstanceInt, LocalStaticInstance>
{
public:
    virtual ~LocalStaticInstanceInt()
    {
        cout << "destory LocalStaticInstanceInt" << endl;
    }
    int getValue() const {return value;}
    void setValue(int val) {value = val;}
    friend class LocalStaticInstance;
private:
    LocalStaticInstanceInt():value(0)
    {
        cout << "create LocalStaticInstanceInt" << endl;
    }
    int value;
};
//使用懒惰初始化单例类  默认
class LocalStaticInstanceString: public Singleton<LocalStaticInstanceString>
{
public:
    ~LocalStaticInstanceString()
    {
        cout << "destory LocalStaticInstanceString" << endl;
    }
    string getValue() const {return value;}
    void setValue(string val) {value = val;}
    friend class LazyInstance;
private:
    LocalStaticInstanceString():value("hello")
    {
        cout << "create LocalStaticInstanceString" << endl;
    }
    string value;
};

LocalStaticInstance和LazyInstance实现了create方法,用于创建单例,而在Singleton的instance的方法中调用create方法

main函数调用方法

int main()
{
    {
        LocalStaticInstanceInt* lsi1 = Singleton<LocalStaticInstanceInt>::instance();
        LocalStaticInstanceInt* lsi2 = Singleton<LocalStaticInstanceInt>::instance();
        cout << "lsi1.value = "<< lsi1->getValue() << "  lsi2.value = " << lsi2->getValue() << endl;
        lsi1->setValue(5);
        cout << "lsi1.value = "<< lsi1->getValue() << "  lsi2.value = " << lsi2->getValue() << endl;
    }
    {
        LocalStaticInstanceString* lss1 = Singleton<LocalStaticInstanceString>::instance();
        LocalStaticInstanceString* lss2 = Singleton<LocalStaticInstanceString>::instance();
        cout << "lsi1.value = "<< lss1->getValue() << "  lsi2.value = " << lss2->getValue() << endl;
        lss1->setValue("world");
        cout << "lsi1.value = "<< lss1->getValue() << "  lsi2.value = " << lss2->getValue() << endl;
    }
}

输出

输出完美,现在的代码非常简洁,是不是很神奇啊。 我们继续往下探索

5 线程安全

因为我们使用的是单例,全程序只有一个实例,如果有多个线程修改这个单例,就有可能出现多个线程同时修改的情况,那么这就是线程不安全的,那么我们很有必要对公共资源的同步控制,那么最简单的方法就是加一把锁。

因为我们都是学Qt的,那么小豆君就给它的参数默认为QMutex

//Singleton类对外提供一致接口,默认调用LazyInstance
template <class T, class InstancePolicy=LazyInstance, class LockType = QMutex>
class Singleton : private InstancePolicy
{
public:
    static T* instance();
};
//在类声明之外定义instance接口
template <class T, class InstancePolicy, class LockType>
T* Singleton<T, InstancePolicy, LockType>::instance()
{
    static T* ptr = 0;
    static LockType lock;
    if(!ptr)
    {
        QMutexLocker ml(&lock);
        if(!ptr)
            InstancePolicy::create(ptr);
    }
    return const_cast<T*>(ptr);
}

6 禁用复制构造函数

要达到一个完整的单例,还需要禁用拷贝构造函数和赋值构造函数,为此我们可以创建一个基类A,将A的拷贝构造函数和赋值构造函数声明设为私有,默认构造函数声明为受保护的,让Singleton<>私有继承A。那么凡是由Singleton<>派生的类就都被禁用了拷贝构造函数和赋值构造函数。因为A的拷贝构造和赋值构造在Singleton<>中都已经变成私有的了,当然Singleton<>的派生类就不可能在调用复制构造函数了。

//禁止复制基类
class NonCopyable
{
    NonCopyable(const NonCopyable&);
    const NonCopyable& operator=(const NonCopyable&);
protected:
    NonCopyable() {}
    ~NonCopyable() {}
};
//Singleton类对外提供一致接口,默认调用LazyInstance
//私有继承NonCopyable,禁止复制
template <class T, class InstancePolicy=LazyInstance, class LockType = QMutex>
class Singleton : private InstancePolicy,private NonCopyable
{
public:
    static T* instance();
};
//在类声明之外定义instance接口
template <class T, class InstancePolicy, class LockType>
T* Singleton<T, InstancePolicy, LockType>::instance()
{
    static T* ptr = 0;
    static LockType lock;
    if(!ptr)
    {
        QMutexLocker ml(&lock);
        if(!ptr)
            InstancePolicy::create(ptr);
    }
    return const_cast<T*>(ptr);
}

7 完整的单例类模板

在做了这么多探索和分析之后,我们终于可以看看最终的代码了。

noncopyable.h

#ifndef NONCOPYABLE_H
#define NONCOPYABLE_H
class NonCopyable
{
    NonCopyable(const NonCopyable&);
    const NonCopyable& operator=(const NonCopyable&);
protected:
    NonCopyable() {}
    ~NonCopyable() {}
};
#endif // NONCOPYABLE_H

singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H
#include <QMutexLocker>
#include <cassert>
#include "noncopyable.h"
///--start 局部静态变量单例类实现
//注意这是类,而不是模板,它实现了下面Singleton模板的create方法
class LocalStaticInstance {
protected:
    template <class T>
    static void create(T*& ptr)
    {
        static T instance;
        ptr = &instance;
    }
};
///--end
///--start 懒惰初始化单例类实现
//释放器 用于释放在堆上分配的单例
template <class T>
class Destroyer
{
    T* doomed;
public:
    Destroyer(T* q) : doomed(q)
    {
        assert(doomed);
    }
    ~Destroyer();
};
//在类外实现的析构函数,注意析构函数开头要写成Destroyer<T>形式
//因为Destroyer<T>才是一个真正的类型
template <class T>
Destroyer<T>::~Destroyer()
{
    try
    {
        if(doomed)
            delete doomed;
    } catch(...) { }
    doomed = 0;
}
class LazyInstance
{
protected:
    template <class T>
    static void create(T*& ptr)
    {
        ptr = new T;
        static Destroyer<T> destroyer(ptr);
    }
};
///--end
///--start Singleton类实现
//对外提供一致接口,默认调用LazyInstance,QMutex,你也可以换成其它库的锁
template <class T, class InstancePolicy=LazyInstance, class LockType = QMutex>
class Singleton : private InstancePolicy,private NonCopyable
{
protected:
    Singleton(){}
public:
    static T* instance();
};
//在类声明之外定义instance接口
template <class T, class InstancePolicy, class LockType>
T* Singleton<T, InstancePolicy, LockType>::instance()
{
    static T* ptr = 0;
    static LockType lock;
    if(!ptr)
    {
        QMutexLocker ml(&lock);
        if(!ptr)
        {
            InstancePolicy::create(ptr);
        }
    }
    return const_cast<T*>(ptr);
}
///--end
#endif // SINGLETON_H

好了,关于单例类模板的详细介绍终于写完了,文中涉及了多个编程技巧,还需你慢慢品味,也欢迎在评论区留言讨论。


最后也希望大家多多支持小豆君的创作,关注小豆君的公众号“小豆君Qt分享”,最新文章都会在公众号第一时间发布。

相关文章
|
6月前
|
C++
C++实现单例模式-多种方式比较
单例模式,面试中经常被问到,但是很多人只会最简单的单例模型,可能连多线程都没考虑到,本文章从最简单的单例,到认为是最佳的单例模式实现方式,单例模式没有什么知识点,直接上源码
88 0
|
设计模式 存储 安全
八种创建单例模式的方式-懒汉式与饿汉式及枚举
八种创建单例模式的方式-懒汉式与饿汉式及枚举
121 2
|
设计模式 安全 Java
JAVA设计模式1:单例模式,确保每个类只能有一个实例
JAVA设计模式1:单例模式,确保每个类只能有一个实例
109 0
|
设计模式 安全 Java
特殊类设计及单例模式(C++)
特殊类设计及单例模式(C++)
75 1
|
6月前
|
设计模式 Java C++
【C++高阶(八)】单例模式&特殊类的设计
【C++高阶(八)】单例模式&特殊类的设计
|
安全 Java 编译器
单例模式的4种实现方式
单例模式的4种实现方式
102 0
|
6月前
|
设计模式 安全 Java
Java设计模式—单例模式的实现方式和使用场景
那么为什么要有单例模式呢?这是因为有的对象的创建和销毁开销比较大,比如数据库的连接对象。所以我们就可以使用单例模式来对这些对象进行复用,从而避免频繁创建对象而造成大量的资源开销。
149 1
|
6月前
单例模式例子
单例模式例子
|
设计模式 存储 索引
单例设计、多例设计、工厂设计模式、枚举的介绍及使用
单例设计、多例设计、工厂设计模式、枚举的介绍及使用
140 0
|
设计模式 数据库连接 数据库
单例(Singleton)设计模式应用场景
单例(Singleton)设计模式应用场景
140 0