c++ 单例模式

单例模式主要要两种实现,一种是饿汉模式,另一种是懒汉模式。
饿汉模式就是在类一开始生成时就产生,而懒汉是要等到调用时,才生成。
下面分别讲解这两种模式。

懒汉

v1: not thread safe

class Singleton{
private:
    static Singleton*instance;
    Singleton(){
        cout<<"Singleton ctor"<<endl;
    }//let the ctor private
public:
    static Singleton* getInstance(){
        if(instance==NULL)
            instance=new Singleton;
        return instance;
    }
};
Singleton* Singleton::instance=NULL;

v2 not thread safe

下面这种,与上面那种是等价的。

class Singleton{
private:
    Singleton(){
        cout<<"Singleton ctor"<<endl;
    }

public:
    static Singleton* getInstance(){
        static Singleton instance;  //is equal version1 .not thread safe
        return &instance;
    }

};

v3 thread safe

class Singleton{
private:
    static Singleton*instance;
    Singleton(){
        cout<<"Singleton ctor"<<endl;
    }
    ~Singleton(){
        cout<<"Singleton dtor"<<endl;
    }

    class Garbo{
    public:
        ~Garbo(){
            if(Singleton::instance)
                delete instance;
        }
    };
    //static Garbo garbo;
public:
    static mutex mutex_;
    static void fun(){
        delete instance;
    }
    static Singleton* getInstance(){
        lock_guard<mutex> lock(mutex_);
        if(instance==NULL){
            instance=new Singleton;
            atexit(fun);
        }

        return instance;
    }
};
mutex Singleton::mutex_;
Singleton* Singleton::instance;
//Singleton::Garbo Singleton::garbo;

其实线程安全也就是在getInstance函数中加锁而已,但这里会有两个问题,第一个是这个加锁位置会导致每次访问getInstance都会锁住,导致效率较低,这个问题会在下面解决;
另外一个问题就是,当没有人会调用类时,怎么释放Singleton*指针?这里有两种方法解决,一种是如上程序中,将释放指针的函数传给ateixt,这样,在程序释放时,就会执行该函数,从而释放指针;还有一种解决方法是定义一个成员类,在销毁类的时候就会销毁成员类,我们只要在该成员类的析构函数中调用释放Singleton*指针的代码,就可以了。
为了解决上面提出的效率低下问题,我们引出下面这种,dcl(double check lock)

v3 not thread safe

class Singleton{
private:
    Singleton(){
        cout<<"ctor"<<endl;
    }
    static Singleton* instance;
public:
    static mutex mtx;
    static Singleton* getInstance(){
        if(instance==NULL){            //1
            lock_guard<mutex> lck(mtx);//2
            if(instance==NULL){        //3
                instance=new Singleton;//4
            }

        }
        return instance;
    }
};
Singleton* Singleton::instance;
mutex Singleton::mtx;

如果没有3的话,会导致一种情况,比如说,多个线程都通过了1,到达2,这时候只有一个能通过2,设为a线程,其他都阻塞在2,这时候这个通过的a线程继续执行4,然后出来,这时候,刚刚那些线程会有一个能通过2,设为b,因为刚刚进去的线程a已经出来,锁已经释放,所以b线程能够进入,如果没有3这个判断的话,线程b就又会重新构造一个Singleton。
这种double check lock 方法在很长一段时间被很多人认为是对的,只到有个大神发现了问题,这个问题就出在new上面
其实new这个关键字完成三件事

a 调用new 操作符,分配空间
b 调用类的构造函数
c 将指针返回

我们之前觉得double check lock是对的,是因为我们觉得new做这三件事按照上面顺序的,但是,这个其实是不一定的,并没有规定说,new 关键字要按上面顺序来,所以,加入顺序是acb的话,就会出现一个线程x在位于4这个位置时,有另一个线程y刚刚进来,位于1处,但是因为此时,new 关键字已经执行到c处,instance已经不是null了,所以线程y判断instance!=null,跨过2,直接返回给程序用,但是,其实这个时候instance是还没初始化的。
所以要解决这个问题,就有了下面这个版本

v thread safe

class Singleton{
private:
    static Singleton*instance;
    static pthread_once_t  ponce;
    Singleton(){
        cout<<"Singleton ctor"<<endl;
    }//let the ctor private
    static void init(){
        instance=new Singleton;
    }
public:
    static Singleton* getInstance(){
        pthread_once(&ponce,init);
        return instance;
    }
};
Singleton* Singleton::instance=NULL;
pthread_once_t  Singleton::ponce=PTHREAD_ONCE_INIT;

这种方法实际上是借助了linux下的pthread_once在多线程下只会执行一次的特性。


饿汉

class Singleton{
private:
    Singleton(){
        cout<<"Singleton ctor"<<endl;
    }
    static Singleton* instance;
public:
    static Singleton* getInstance(){
        return instance;
    }

};
Singleton* Singleton::instance=new Singleton();

总结一下,单例模式懒汉模式getInstance函数必须为static,而且里面用到的东西,如fun函数,mutex都必须为static,然后c++ 单例最好的实现是利用pthread_once,这样就可以避免double check lock的异常和直接加锁的效率抵下。然后特别注意,c++成员中的static成员变量,类中的只是声明而已,并不是定义,所以必须在类外定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值