设计模式-单例模式

本文探讨了单例模式中的饿汉模式,建议使用全局区的普通对象而非堆上的指针对象,因为这样可以避免不必要的堆内存分配,并在程序结束时自动调用析构函数。同时,通过私有化构造函数、拷贝构造函数和赋值构造函数来确保单例的唯一性。对于指针对象,可以通过内嵌类的方式在程序结束时自动调用析构函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式分懒汉模式和饿汉模式。本文对饿汉子模式中的一些细节进行简要说明。
1、单例类的实例化可以采用堆也可以采用全局区,分别对应指针对象和普通对象。指针对象的地址需要使用static 修饰,所以也是保存在全局区,指针对象是通过new出来的,所以对应类的大小内存是在堆上开辟的。那是使用指针对象还是普通对象呢?
我建议使用普通对象,有两个原因。一是单例类的实例化,一般不释放,而堆却是动态的意思,所以没必要将一般不释放的内存在动态的堆上开辟。二是程序结束时,系统会释放所有的全局变量,包括这里的static普通对象,这样就可以自动调用析构函数处理其它需要关闭文件之类的操作,可以参考后文。
static成员均需要类内声明,类外cpp文件初始化。两者写法如下。

.h文件如下:

class CReject{
public:
    static CReject* GetInstance()
    {
        return spInst;return &Inst;
    }
private:
    static CReject* spInst;
    static CReject Inst;
    //static EchReject* spInst = new EchReject;//这是错误的
};

.cpp文件如下

CReject* CReject::spInst = new CReject();//或者new CReject;
CReject CReject::Inst;

new CReject() 和new CReject 没有区别。对于系统内置的类型,比如new int和new int()有区别,带括号具有初始化的作用。

2、保证单例
如何保证单例?先看看不保证单例的设计:

class CReject{
public:
    static CReject* GetInstance()
    {
        return spInst;
    }
    void PrintValue(){qDebug()<<"PrintValue"<<mValue;}
    void SetValue(int v){mValue=v;}
public:
    CReject(const CReject&e)    //拷贝构造函数
    {
        qDebug()<<"重写拷贝构造函数";
        mValue = e.mValue;
    }
    CReject& operator=(const CReject&e) //赋值函数
    {
        qDebug()<<"重写赋值函数";
        return *this;
    }
public:
    CReject(){qDebug()<<"普通构造函数";}
    ~CReject(){qDebug()<<"析构函数"<<mValue;}
private:
    static CReject* spInst;
    int mValue;
};
CReject* CReject::spInst = new CReject();
void main(int argc, char *argv[])
{
	  //在main执行之前,就饿汉子模式实例化了类的指针对象,调用了普通构造函数。
    qDebug()<<"main";
    CReject *pInst = CReject::GetInstance();
    pInst->SetValue(1);
    pInst->PrintValue();

    CReject e2 = *pInst;   //调用拷贝构造函数,程序结束时会调用析构函数
    e2.PrintValue();			 //打印了拷贝过来的值1
    e2.SetValue(2);
    e2.PrintValue();

    qDebug()<<"朴实的分界线----------";
    int buff[10];
    CReject *p3 = (CReject*)&buff[0];
    *p3 = *pInst;          //调用赋值构造函数,程序结束时,不会调用析构函数 
      
    CReject e9;     			//调用普通的构造函数,程序结束时会调用析构函数
    e9.SetValue(9);
}

程序输出结果如下。

普通构造函数
main
PrintValue 1
重写拷贝构造函数
PrintValue 1
PrintValue 2
朴实的分界线----------
重写赋值函数
普通构造函数
析构函数 9
析构函数 2

由此,可以看出,将构造函数、拷贝构造函数、赋值构造函数私有化或者将析构函数私有化,就可以保证单例。

private:
    CReject(const CReject&e);    //仅声明即可
    CReject& operator=(const CReject&e) //赋值构造函数,仅声明即可
    {
        qDebug()<<"重写赋值构造函数";
        return *this;
    }
    CReject(){qDebug()<<"普通构造函数";}
public:
    ~CReject(){qDebug()<<"析构函数"<<mValue;}

//将构造函数、拷贝构造函数、赋值构造函数私有化,e2 e9 p3都会报错
private:
    ~CReject(){qDebug()<<"析构函数"<<mValue;}
    
//将析构函数私有化,声明e2和e9就会报错,但p3不会报错。
    CReject *pInst = CReject::GetInstance();
    pInst->SetValue(1);
    pInst->PrintValue();

//    CReject e2 = *pInst;   //报错
//    e2.PrintValue();
//    e2.SetValue(2);
//    e2.PrintValue();

    qDebug()<<"朴实的分界线----------";
    int buff[10];
    CReject *p3 = (CReject*)&buff[0];
    *p3 = *pInst;          //调用赋值构造函数,程序结束时,不会调用析构函数

//    CReject e9;     		 //报错
//    e9.SetValue(9);

3、针对指针对象,程序结束时如何自动调用析构函数。
方法很多,这里介绍最常用的。先看看不调用析构的设计。

class CReject{
public:
    static CReject* GetInstance()
    {
        return spInst;
    }
    void PrintValue(){qDebug()<<"PrintValue"<<mValue;}
    void SetValue(int v){mValue=v;}
private:
    CReject(const CReject&e);
    CReject& operator=(const CReject&e);
    CReject(){qDebug()<<"普通构造函数";}
    //CReject();		//构造函数不能仅声明,因为有实例化
public:
    ~CReject(){qDebug()<<"析构函数"<<mValue;}
private:
    static CReject* spInst;
    int mValue;
};
CReject* CReject::spInst = new CReject();
void main(int argc, char *argv[])
{
    qDebug()<<"main";

    CReject *pInst = CReject::GetInstance();
    pInst->SetValue(1);
    pInst->PrintValue();
}

程序没有调用析构函数,运行结果:

普通构造函数
main
PrintValue 1

内嵌类的实例对象。内嵌类是为了防止其他人使用这个类,全部的实例对象在程序结束时会被析构。利用这个特点,来析构我们想析构的对象。

class CReject{
public:
    static CReject* GetInstance()
    {
        return spInst;
    }
    void PrintValue(){qDebug()<<"PrintValue"<<mValue;}
    void SetValue(int v){mValue=v;}
private:
    CReject(const CReject&e);
    CReject& operator=(const CReject&e);
    CReject(){qDebug()<<"普通构造函数";}
public:
    ~CReject(){qDebug()<<"析构函数"<<mValue;}
private:
    class CRejectDistruct{		//新增一个内嵌类
    public:
        ~CRejectDistruct(){
            if(spInst != NULL)
            {
                qDebug()<<"~CRejectDistruct";
                delete spInst;		//析构我们的指针对象
            }
        }
    };
private:
    static CReject* spInst;
    static CRejectDistruct sdis;		//内嵌类的实例化声明
    int mValue;
};
CReject* CReject::spInst = new CReject();
CReject::CRejectDistruct CReject::sdis;		//必须要有初始化的操作,static的仅声明不行
void main(int argc, char *argv[])
{
    qDebug()<<"main";

    CReject *pInst = CReject::GetInstance();
    pInst->SetValue(1);
    pInst->PrintValue();
}

程序结束时,调用了析构函数,运行结果:

普通构造函数
main
PrintValue 1
~CRejectDistruct
析构函数 1

4、补充一个做这个文章时,中间遇到的类实例化问题。

class CR
{
public:
    int mv;
    static CR* spInst;
    CR& operator=(const CR &e){qDebug()<<"赋值构造函数";}
    ~CR(){qDebug()<<"~CR";}
};

CR* CR::spInst = new CR();
//情况1
CR *pcr;			//非法操作,这里使用了栈上的野指针
qDebug()<<pcr->mv;
pcr->mv = 5;
qDebug()<<pcr->mv;

//情况2
int buff[10];
CR *pcrInst = CR::spInst;
CR *pcr = (CR*)&buff[0];		//并没有实例化类,只是栈上指针的强制类型转换
*pcr = *pcrInst;			//会调用赋值构造函数,程序结束时不调用析构函数
qDebug()<<pcr->mv;
pcr->mv = 5;
qDebug()<<pcr->mv;
	
//情况3
CR *pcrInst = CR::spInst;
CR *pcr;
pcr = pcrInst;			//合法操作,一个指针类型赋值给另一个指针类型
qDebug()<<pcr->mv;
pcr->mv = 5;
qDebug()<<pcr->mv;	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值