单例模式——懒汉、饿汉

1.单例模式:单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。(在内存中只会创建且仅创建一次对象的设计模式。)2.为什么要有单例模式?单例模式是为了保证程序的线程安全。

什么是线程安全?在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

如何保证线程安全?给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用。让线程也拥有资源,不用去共享进程中的资源。如:使用threadlocal可以为每个线程维护一个私有的本地变量。

3.应用场景:为多个客户端服务的服务器对象、数据库连接池、多线程线程池、应用程序的日志应用、操作系统的文件系统、windows(任务管理器、回收站)

4.单例模式特点:1)将构造函数设置为私有的,让其在外部不能创建对象2)删除拷贝构造函数,防止外部拷贝,确保实例的唯一性3)删除赋值运算符重载函数,对于单例模式的简单类型(没有指针、没有动态获取资源、没有虚函数、没有互斥量的类类型),它可以是int、char等类型,所以可以通过赋值来构建对象,防止其外部赋值确保实例的唯一性。

class Object
{
private:
    int value;
private:
    Object(int x = 0) :value(x) { cout << "create object" << endl; }//让其在外部不能创建对象
public:
    /*面试:如果不删除这两个函数会怎样?*/
    /*编译器在编译的时候会产生缺省的拷贝构造函数和赋值函数*/
    /*面试:如果不写这两个函数会怎样?*/
    /*编译器会产生缺省的拷贝构造函数和赋值函数*/
    /*空类型有六个缺省的函数:1.构造 2.析构 3.拷贝构造 4.赋值 5.取地址符 6.const取地址符*/
    Object(const Object&) = delete;//删除拷贝构造,防止对象通过拷贝构造创建对象
    Object& operator=(const Object&) = delete;//删除赋值,防止简单类型通过赋值创建对象
public:
    ~Object() { cout << "destroy object" << endl; }//防止某些情况不能正常释放,所以不设置成私有
}; 

5.单例模式的实现:

1)饿汉模式:在类加载时已经创建好该单例类对象,等待被程序使用

2)懒汉模式:在真正需要使用对象时才去创建该单例类对象

模式的区别

饿汉模式

//饿汉模式(饥汉模式)
1.将构造函数设置为私有的,删除拷贝构造和赋值函数,防止在外部创建对象且保证对象仅被创建一次
2.类内设置静态对象,在类外将此对象实例化(初始化)
3.类中有一个获取实例的静态方法,可以全局访问
class Object
{
private:
    int value;
    //Object objx;//error,是不完整类型,因为类类型中不能出现类类型自身的的对象,在构建对象时会引起无穷递归
    static Object objx;//不是实例对象的成员,因为静态成员只有一份,被所有对象共享,不存在某个对象的独立空间中
private:
    Object(int x = 0) :value(x) { cout << "create object" << endl; }//让其在外部不能创建对象

public:
    Object(const Object&) = delete;//删除拷贝构造,防止对象通过拷贝构造创建对象
    Object& operator=(const Object&) = delete;//删除赋值,防止简单类型通过赋值创建对象
public:
    ~Object() { cout << "destroy object" << endl; }//防止某些情况不能正常释放
    //void SetValue(Object* const this,int x=0)
    void SetValue(int x = 0) { value = x; }
    //int GetValue(const Object* const this)
    int GetValue() const { return value; }
    static Object& GetObject()//此时必须引用返回,因为如果值返回,产生将亡值需要调用拷贝构造,但是我们已经将拷贝构造删除了
    {
        return objx;//获取实例的静态方法
    }
};

Object Object::objx(10);//静态成员在类外初始化

int main()
{
    Object& obja = Object::GetObject();
    Object& objb = Object::GetObject();
    //此时obja和objb是同一个对象 objx.value=10  obja.value=10   objb.value=10
    //这就是单例模式,对象只能被创建一份

    obja.SetValue(100);//objx.value=10  obja.value=100   objb.value=100
    cout << objb.GetValue() << endl;
}

注意:类类型中不能出现类类型自身的的对象,否则在构建对象时会引起无穷递归

class Object
{
private:
    int value;
    Object objx;//error,是不完整类型,因为类类型中不能出现类类型自身的的对象,在构建对象时会引起无穷递归
private:
    Object(int x = 0) :value(x) { cout << "create object" << endl; }
public:
    Object(const Object&) = delete;
    Object& operator=(const Object&) = delete;
public:
    ~Object() { cout << "destroy object" << endl; }
};

int main()
{
    Object obja;    //Obja.value
                        .objx  .value
                               .objx   .value
                                       .objx 
  obja中的objx还有objx,会引起无穷递归
} 

半个饿汉模式(半个懒汉模式)

//半个懒汉模式(半个饿汉模式) 
1.将静态对象的创建放在获取实例静态方法中,在获取实例函数被调用的时候才创建对象
单线程中只创建了一个对象
class Object
{
private:
    int value;
private:
    Object(int x = 0) :value(x) { cout << "create object" << endl; }//让其在外部不能创建对象

public:
    Object(const Object&) = delete;//删除拷贝构造,防止对象通过拷贝构造创建对象
    Object& operator=(const Object&) = delete;//删除赋值,防止简单类型通过赋值创建对象
public:
    ~Object() { cout << "destroy object" << endl; }//防止某些情况不能正常释放
    //void SetValue(Object* const this,int x=0)
    void SetValue(int x = 0) { value = x; }
    //int GetValue(const Object* const this)
    int GetValue() const { return value; }
    static Object& GetObject()//获取实例的静态方法
    {
        static Object objx;
        return objx;
    }
};

int main()
{
    Object& obja = Object::GetObject();//调用GetObject(),创建静态对象static Object objx(调用构造函数)
    Object& objb = Object::GetObject();//调用GetObject(),不再创建objx对象,直接return objx,因为静态对象只被创建一次则不会调用构造函数
    //此时obja和objb是同一个对象,构造函数只调用了一次 objx.value=0  obja.value=0   objb.value=0
} 

//半个懒汉模式(半个饿汉模式)缺点:
 //如果此设计模式是单线程没有什么问题,但如果是多线程则会线程不安全
//1.在多线程中,假如有两个线程函数funa和funb,有可能静态对象objx在同一个地址被创建两次
//2.有可能执行obja.SetValue(100);被打断,返回的objx不是100而是缺省值0,则线程不安全
 


void funa()//执行此线程调用GetObject(),创建static Object objx
{
    Object& obja = Object::GetObject();
    obja.SetValue(100);
}

void funb()
{
    Object& objb = Object::GetObject();//执行此线程调用GetObject(),创建static Object objx
    cout << objb.GetValue() << endl;
}

 

懒汉模式

//懒汉模式
1.将构造函数设置为私有的,删除拷贝构造和赋值函数,防止在外部创建对象且保证对象仅被创建一次
2.类内设置静态本类类型指针,类外初始化
3.定义创建对象的静态方法,在真正使用时调用此静态方法创建对象
#include<iostream>
#include<thread>//C++11的线程库
using namespace std;
class Object{
private:
    int value;
    static Object* pobj;
private:
    Object(int x = 0) :value(x) { cout << "create object" << endl; }//让其在外部不能创建对象

public:
    Object(const Object&) = delete;//删除拷贝构造,防止对象通过拷贝构造创建对象
    Object& operator=(const Object&) = delete;//删除赋值,防止简单类型通过赋值创建对象
public:
    ~Object() { cout << "destroy object" << endl; }//防止某些情况不能正常释放
    //void SetValue(Object* const this,int x=0)
    void SetValue(int x = 0) { value = x; }
    //int GetValue(const Object* const this)
    int GetValue() const { return value; }
    static Object& GetRefObject()//获取实例的静态方法
    {
        if (nullptr == pobj)//在GetRefObject()函数被调用时才创建对象
        {
            pobj = new Object(10);
        }
        return *pobj;
    }
};

Object* Object::pobj = nullptr;//静态成员在类外初始化

//懒汉模式的缺点:
//1.线程不安全
void funa()
{
    Object& obja = Object::GetRefObject();
    obja.SetValue(100);
    cout << "&obja  " << &obja << endl;
}

void funb()
{
    Object& objb = Object::GetRefObject();
    cout << objb.GetValue() << endl;
    cout << "&objb  " << &objb << endl;
}

int main()
{
    std::thread tha(funa);
    std::thread thb(funb);
    tha.join();//等待线程结束
    thb.join();//等待线程结束
}

上述模式在多线程中,对象被创建了两次(obja和objb地址不同,Object构造函数被调用了两次),并且funb线程执行时将funa线程设置的value值100覆盖掉了,改成了创建时的初始值10.运行结果为:

猜你喜欢

转载自blog.csdn.net/weixin_53472334/article/details/131989186