C++单例模式

一、背景

        单例模式在c++项目中被广泛的使用,主要的场景大概是有一个需要频繁被多个其他对象调用的类,设置成单例模式使调用者更简单高效。刚好今天在一个项目中需要使用这种模式来开发业务。特此记录一下。废话不多说,直接进入主题,分享一下几种单例模式的实现方式

二、为什么要用单例模式

        首先我们得先弄清楚为什么需要单例模式。其实如果只是简单的想调用其他类的成员变量,定义一个类成员即可通讯。但是如果出现这样一种情况,当这个类是需要频繁被外部调用的时候,如果每次都创建一个类,那势必会造成内存的飙升,系统的性能就会下降。所以单例模式就出现了。单例顾名思义就是一直只会有一个实例对象,这样子不管被调用多少次,都只会占用一次内存。其实所谓的单例就是创建一个全局的的访问点,就能很好的规避需要频繁创建对象的问题。

三、单例模式的特点

        1、私有化构造函数,包括拷贝构造,赋值操作符等都需要私有化。这样外界就无法自由的new对象了,进而成功的阻止了多个实例的产生。

        2、类定义中含有该类唯一静态私有对象:静态变量是存储在数据段中的,是唯一的且能让所有对象都访问得到

        3、提供一个共有的静态函数来获取实例

四、懒汉模式的实现

        这种模式简单来说就是在使用对象的时候才回去创建它,不然就懒得去new,俗称懒汉式,

 4-1:基础版本的实现       

        下面就先实现一个简单的实例来直观感受一下,所有的代码可以直接再在线平台 GDB Online调试运行。代码和运行结果都贴在下面。

/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>

using namespace std;

//声明一个单例类
class Singleton
{
private:
    //将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
    Singleton(void){
        cout<<"构造函数Singleton()"<<endl;
    };                  //构造函数
    Singleton(const Singleton & sig); //拷贝构造函数
    ~Singleton();
    Singleton & operator=(const Singleton & sig); //赋值运算符重载
private:
    static Singleton * ins;  //私有静态对象
public:
    static Singleton * getInstance(){  //获取实例对象的静态共有函数,可以传参的
        if(!ins){
            ins = new Singleton();
        }
        return ins;
    };
public:
    void testPrint(){
        cout<<"This is Sigleton test info"<<endl;
    };
};

Singleton * Singleton::ins = nullptr;

int main()
{
    
    Singleton * cSigleton_1 = Singleton::getInstance();
    cSigleton_1->testPrint();
    Singleton * cSigleton_2 = Singleton::getInstance();
    cSigleton_2->testPrint();
    Singleton * cSigleton_3 = Singleton::getInstance();
    cSigleton_3->testPrint();
    cout<<"main end"<<endl;
    return 0;
}

 从结果来看已经实现了单例模式,因为获取了三次实例,只进了一次构造函数,创建了一个实例。但是这个程序至少还存在着两个问题。1,存在线程安全问题; 2,有内存泄漏问题,new出来的空间没有delete。接下来我们逐步优化这两个问题。

4-2:线程安全问题优化

        如果有多个线程同时获取实例,可能会出现new多次实例对象的问题,因为ins在多个线程之间可能同时是空的,这时候这些线程就都会new一个实例出来。要解决这个问题就必须加锁来实现互斥操作,保证只有一个线程去new 实例。改善代码如下所示:

/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>

using namespace std;

//声明一个单例类
class Singleton
{
private:
    //将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
    Singleton(void){
        cout<<"构造函数Singleton()"<<endl;
    };                  //构造函数
    Singleton(const Singleton & sig); //拷贝构造函数
    ~Singleton();
    Singleton & operator=(const Singleton & sig); //赋值运算符重载
private:
    static Singleton * ins;  //私有静态对象
    static mutex m_mutex;
public:
    static Singleton * getInstance(){  //获取实例对象的静态共有函数,可以传参的
        lock_guard<mutex> l(m_mutex);  //必须加锁
        if(!ins){
            ins = new Singleton();
        }
        return ins;
    };
public:
    void testPrint(){
        cout<<"This is Sigleton test info"<<endl;
    };
};

Singleton * Singleton::ins = nullptr;
mutex Singleton::m_mutex;

int main()
{
    thread t1([] {
        Singleton* cSingleton_1 = Singleton::getInstance();
        
        cout << "&cSingleton_1:" << cSingleton_1 << endl;
    });
    thread t2([] {
        Singleton* cSingleton_2 = Singleton::getInstance();
        cout << "&cSingleton_2:" << cSingleton_2<< endl;
    });
    
    t1.join();
    t2.join();
    
    Singleton* cSingleton_3 = Singleton::getInstance();
    cout << "&cSingleton_3:" << cSingleton_3<< endl;
    cSingleton_3->testPrint();
    cout<<"main end"<<endl;
    return 0;
}

 从结果中可以看到,这个三个实例地址都是同一个,说明多线程获取单例类也只是创建了一个实例。现在我们将加锁的那行代码屏蔽运行一下看看结果,如下图所示:

 从上图结果可知,不加锁,就会创建两个实例,每个线程都去创建的实例。仅仅只是把加锁的那行代码屏蔽了。所以可以通过加锁的方式来解决线程不安全的问题

4-3:内存泄漏问题优化

单例类中new出来的实例没有得到释放,所以需要方法来delete这个对象。解决办法有两个:(1):通过智能指针 (2) 通过嵌套类

        1:智能指针方式

        通过将实例变量声明为智能共享指针的方式,在初始化智能指针的时候需要认为的添加公有销毁函数,因为析构函数私有化了,无法在外部调用。

/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>

using namespace std;

//声明一个单例类
class Singleton
{
private:
    //将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
    Singleton(void){
        cout<<"构造函数Singleton()"<<endl;
    };  
    
    Singleton(const Singleton & sig); //拷贝构造函数
    
    ~Singleton(){
        cout<<"析构函数~Singleton()"<<endl;
    };
    
    Singleton & operator=(const Singleton & sig); //赋值运算符重载
    
private:
    static boost::shared_ptr<Singleton> ins;
    static mutex m_mutex;
    
public:
    static boost::shared_ptr<Singleton> getInstance(){  //获取实例对象的静态共有函数,可以传参的
        if(!ins){  //不需要每次都进来获取锁资源,提高效率
            lock_guard<mutex> l(m_mutex);  //必须加锁
            if(!ins){
            ins.reset(new Singleton(), destoryInstance);
            }
        }
        return ins;
    };
    
    static void destoryInstance(Singleton * instance){
        cout<<"销毁单例对象资源(delete ins)"<<endl;
        delete instance;
    };
    
public:
    void testPrint(){
        cout<<"This is Sigleton addr"<< ins << endl;
    };
};

boost::shared_ptr<Singleton> Singleton::ins = nullptr;
mutex Singleton::m_mutex;

int main()
{
    thread t1([] {
        boost::shared_ptr<Singleton> cSingleton_1 = Singleton::getInstance();
        
        cout << "&cSingleton_1:" << cSingleton_1 << endl;
    });
    thread t2([] {
        boost::shared_ptr<Singleton> cSingleton_2 = Singleton::getInstance();
        cout << "&cSingleton_2:" << cSingleton_2<< endl;
    });
    
    t1.join();
    t2.join();
    
    boost::shared_ptr<Singleton> cSingleton_3 = Singleton::getInstance();
    cout << "&cSingleton_3:" << cSingleton_3<< endl;
    cSingleton_3->testPrint();
    cout<<"main end"<<endl;
    return 0;
}

 通过上述日志可以看出,单例对象已经被销毁回收了。智能共享指针需要用到boost库,添加相应的hpp文件 boost/make_shared.hpp

        2,通过内嵌类的方式释放资源

        在单例类中内嵌一个类,并初始化一个静态对象。当程序结束的时候,该对象进到析构的同时,将单例实例删除。这样就能回收单例实例的资源。实现如下

/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>

using namespace std;

//声明一个单例类
class Singleton
{
private:
    //将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
    Singleton(void){
        cout<<"构造函数Singleton()"<<endl;
    };  
    
    Singleton(const Singleton & sig); //拷贝构造函数
    
    ~Singleton(){
        cout<<"析构函数~Singleton()"<<endl;
    };
    
    Singleton & operator=(const Singleton & sig); //赋值运算符重载
    
private:
    class Deleter{
        public: 
            Deleter(){};
            ~Deleter(){
                if(ins != nullptr){
                    cout << "释放单例实例资源"<<endl;
                    delete ins;
                    ins = nullptr;
                }
            };
    };
    static Deleter m_deleter;
    static Singleton * ins;
    static mutex m_mutex;
    
public:
    static Singleton * getInstance(){  //获取实例对象的静态共有函数,可以传参的
        if(!ins){  //不需要每次都进来获取锁资源,提高效率
            lock_guard<mutex> l(m_mutex);  //必须加锁
            if(!ins){
                ins = new Singleton();
            }
        }
        return ins;
    };
    
public:
    void testPrint(){
        cout<<"This is Sigleton addr"<< ins << endl;
    };
};

Singleton::Deleter Singleton::m_deleter;
Singleton * Singleton::ins = nullptr;
mutex Singleton::m_mutex;

int main()
{
    thread t1([] {
        Singleton * cSingleton_1 = Singleton::getInstance();
        
        cout << "&cSingleton_1:" << cSingleton_1 << endl;
    });
    thread t2([] {
        Singleton * cSingleton_2 = Singleton::getInstance();
        cout << "&cSingleton_2:" << cSingleton_2<< endl;
    });
    
    t1.join();
    t2.join();
    
    Singleton * cSingleton_3 = Singleton::getInstance();
    cout << "&cSingleton_3:" << cSingleton_3<< endl;
    cSingleton_3->testPrint();
    cout<<"main end"<<endl;
    return 0;
}

 

从上述打印信息可以看到,单例实例的资源已经被释放,这样就没有内存泄露了。

4-4: 基于静态局部变量的实现方式

        C++11标准规定局部的静态对象在多线程场景下,只有初次访问才会被创建实例,后续都是直接获取。若未创建成功,其他线程就会等待,不会出现竞争的情况。而且资源会自动被销毁释放。这种方式是最便捷也是最被推荐的一种实现单例模式的方式。但是请注意,C++11以前的标准并不支持这项功能。实现代码和调试结果如下:

/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>

using namespace std;

//声明一个单例类
class Singleton
{
private:
    //将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
    Singleton(void){
        cout<<"构造函数Singleton()"<<endl;
    };  
    
    Singleton(const Singleton & sig); //拷贝构造函数
    
    ~Singleton(){
        cout<<"析构函数~Singleton()"<<endl;
    };
    
    Singleton & operator=(const Singleton & sig); //赋值运算符重载

public:
    static Singleton & getInstance(){  //获取实例对象的静态共有函数,可以传参的
        static Singleton ins;
        cout << "单例实例的地址:"<<&ins << endl;
        return ins;
    };
    
public:
    void testPrint(){
        cout<<"This is Sigleton test" << endl;
    };
};

int main()
{
    thread t1([] {
        Singleton& cSingleton_1 = Singleton::getInstance();
    });
    thread t2([] {
        Singleton& cSingleton_2 = Singleton::getInstance();
    });
    
    t1.join();
    t2.join();
    
    
    Singleton& cSingleton_3 = Singleton::getInstance();
    cSingleton_3.testPrint();
    cout<<"main end"<<endl;
    return 0;
}

   

可以看到,每次获取的实例都是同一个地址,而且是通过静态局部变量获取,程序退出的时候系统会自动回收其资源。

五、饿汉模式的实现:

        单例实例在类装载的时候就已构建,静态编译的时候就已经分配好了空间,等到需要用时直接通过静态共有成员接口获取到这个实例即可,不需要临时再分配。因为这种模式一开始就创建,感觉就很急促很饿一样,所以就俗称饿汉式。饿汉模式的实现和懒汉模式相比就是new出来一个全局的类对象。这种方式本身就已经是线程安全了,所以不需要加锁。只需要将申请的资源释放,这里就采用共享指针的方式来实现。代码如下:

/******************************************************************************

Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>

using namespace std;

//声明一个单例类
class Singleton
{
private:
    //将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
    Singleton(void){
        cout<<"构造函数Singleton()"<<endl;
    };  
    
    Singleton(const Singleton & sig); //拷贝构造函数
    
    ~Singleton(){
        cout<<"析构函数~Singleton()"<<endl;
    };
    
    Singleton & operator=(const Singleton & sig); //赋值运算符重载
    
    
public:
    static boost::shared_ptr<Singleton> getInstance(){  //获取实例对象的静态共有函数,可以传参的
        return ins;
    };
    
    static void destoryInstance(Singleton * instance){
        cout<<"销毁单例对象资源(delete ins)"<< instance <<endl;
        delete instance;
    };
    
private:
    static boost::shared_ptr<Singleton> ins;
    
public:
    void testPrint(){
        cout<<"This is Sigleton addr:"<< ins << endl;
    };
};

boost::shared_ptr<Singleton> Singleton::ins(new Singleton(), destoryInstance);

int main()
{
    thread t1([] {
        boost::shared_ptr<Singleton> cSingleton_1 = Singleton::getInstance();
        
        cout << "&cSingleton_1:" << cSingleton_1 << endl;
    });
    thread t2([] {
        boost::shared_ptr<Singleton> cSingleton_2 = Singleton::getInstance();
        cout << "&cSingleton_2:" << cSingleton_2<< endl;
    });
    
    t1.join();
    t2.join();
    
    boost::shared_ptr<Singleton> cSingleton_3 = Singleton::getInstance();
    cout << "&cSingleton_3:" << cSingleton_3<< endl;
    cSingleton_3->testPrint();
    cout<<"main end"<<endl;
    return 0;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟~阿斌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值