美文网首页C++Qt学习
超经典单例模板类详解

超经典单例模板类详解

作者: 223480235e8e | 来源:发表于2018-09-12 22:23 被阅读7次

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

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

    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;
        }
    }
    

    以下是输出结果:


    image.png

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

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

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

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

    再次运行程序


    image.png

    这回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>类型的局部静态变量,并且以LazyInstance的实例指针作为参数,那么当程序结束时,会自动调用Destroyer<LazyInstance>的析构函数,而在析构函数中,释放了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;
    }
    

    运行程序


    image.png

    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;
        }
    }
    

    输出


    image.png

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

    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
    
    

    好了,关于单例类模板的详细介绍终于写完了,相信大家也学到了不少好东西,最后不要忘了给小豆君点个赞哦。

    相关文章

      网友评论

        本文标题:超经典单例模板类详解

        本文链接:https://www.haomeiwen.com/subject/brzrgftx.html