C++实现懒汉式单例模式

来源:互联网 发布:dede cms流量整站源码 编辑:程序博客网 时间:2024/06/05 09:36

单例模式无论在生活中还是在工程中都有很广泛的应用,在C++项目中,很多时候我们只希望整个工程中某个类仅有一个实体对象,设计这种类的时候就需要使用单例模式来设计。下面是实现的一个懒汉式单例模式的代码:

#include <iostream>#include <cstdio>using std::cout;using std::endl;class Singleton {    private :        static Singleton* p_obj;        Singleton(void){        }    public :        static Singleton* GetInstance(void){            if(p_obj == NULL){                p_obj = new Singleton();                cout << "New Singleton();" << endl;                return p_obj;            }        }        ~Singleton(void){            if(p_obj!=NULL){                delete p_obj;                p_obj = NULL;                cout << "~Singleton" << endl;            }        }};Singleton* Singleton::p_obj = NULL;void run(void){    Singleton* s1 = Singleton::GetInstance();    Singleton* s2 = Singleton::GetInstance();    cout << "ADDR s1:" << s1 << endl;    cout << "ADDR s2:" << s2 << endl;     }int main(){    run();        cout << "Press enter to continue..." << endl;    getchar();    return 0;}

在设计类的时候,声明了一个static Singleton类型的指针在类的私有成员区,需要注意的是,这个指针的内存单元并不会在对象中开辟,需要程序员自己在全局定义一个Singleton指针,如果不是单例模式的类,这种static类型的成员是每一个对象共享的,即所有的对象都可以访问在全局数据区的这一段static内存。

在类私有成员区还定义了构造函数,显然地,这样设计时不想让程序员可以直接创建Singleton对象,而是需要创建Singleton指针使用GetInstance方法去创建一个对象。这里需要我们知道C++编译器的一个特性:当程序员在设计类的时候,没有显示地声明构造函数,编译器会默认提供一个空的构造函数。这种懒汉式单例模式的实现,就是利用了C++的这一特性,将构造函数声明在类私有成员区,如果程序员在外部创建Singleton对象,编译器将报错:Singleton成员函数时私有的,不可访问。

如果想要调用Singleton类中的GetInstance函数创建对象,首先会判断一下p_obj指针是不是为空,即是不是还没有开辟过这个对象,如果确实目前还没有开辟过这个对象,那么使用new关键字在堆空间中开辟一段Sngleton大小的内存给申请者,而如果程序中早已存在Singleotn对象,则把已经存在的Singleton对象在堆空间中的地址返回给申请者。

其实这段程序是有漏洞的,在类中实现的析构函数并不会释放堆内存,因为它根本就没有被调用。这就造成了堆内存泄露的灾难。不能够在析构中释放内存,而是需要调用者手动地释放堆内存。改进这个漏洞后的代码如下:

#include <iostream>#include <cstdio>using std::cout;using std::endl;class Singleton {    private :        static Singleton* p_obj;        Singleton(){        }    public :        static Singleton* GetInstance(){            if(p_obj == NULL){                p_obj = new Singleton();                cout << "New Singleton();" << endl;                return p_obj;            }        }};Singleton* Singleton::p_obj = NULL;void run(){    Singleton* s1 = Singleton::GetInstance();    Singleton* s2 = Singleton::GetInstance();    cout << "ADDR s1:" << s1 << endl;    cout << "ADDR s2:" << s2 << endl;        delete s1;}int main(){    run();        cout << "Press enter to continue..." << endl;    getchar();    return 0;}

显然,改进了的代码在单线程系统中不会再造成堆内存泄露。为什么要强调一下是单线程的系统中呢?因为它在多线程的系统中还是可能出问题,想象一下,同时两个线程运行到了if(p_obj == NULL),这时候会怎样?显然,会在堆内存中申请两个Singleton大小的内存段。然而我们只释放了一个,想象中他们是同一段,这就造成了泄露了一段Singleton大小的内存。改进这个漏洞的方法是在if(NULL != p_obj)前后加锁,改进如下:

#include <iostream>#include <cstdio>using std::cout;using std::endl;class Singleton {    private :        static Singleton* p_obj;        Singleton(){                    }    public :        static Singleton* GetInstance(){            lock();            if(p_obj == NULL){                p_obj = new Singleton();                cout << "New Singleton();" << endl;                return p_obj;            }            unlock();        }};Singleton* Singleton::p_obj = NULL;void run(){    Singleton* s1 = Singleton::GetInstance();    Singleton* s2 = Singleton::GetInstance();    cout << "ADDR P1:" << s1 << endl;    cout << "ADDR P2:" << s2 << endl;        delete s1;}int main(){    run();        cout << "Press enter to continue..." << endl;    getchar();    return 0;}

然而这样做的在线程很多的时候会影响代码的效率,试想当多个线程都想要申请这么这段内存的时候,都被调度器阻塞,堆在一起等待,这是一件多可怕的事情。为了提高代码的运行效率,可以使用双重判断加锁,改进如下:

static Singleton* GetInstance(){    if(p_obj == NULL){        lock();        if(p_obj == NULL){            p_obj = new Singleton();            cout << "New Singleton();" << endl;            return p_obj;        }        unlock();    }}

多判断一次,减少多线程共同排队到这个位置的可能性。

原创粉丝点击