当前位置: 首页 > news >正文

C++ 单例对象自动释放(保姆级讲解)

目录

单例对象自动释放(重点*)

方式一:利用另一个对象的生命周期管理资源

方式二:嵌套类 + 静态对象(重点)

方式三:atexit + destroy

方式四:atexit + pthread_once


单例对象自动释放(重点*)

在类与对象的章节,我们学习了单例模式。单例对象由静态指针_pInstance保存,最终通过手动调用destroy函数进行释放。

现实工作中,单例对象是需要进行自动释放。程序在执行的过程中 ,需要判断有哪些地方发生了内存泄漏 ,此时需要工具valgrind的使用来确定。假设单例对象没有进行自动释放 ,那么valgrind工具会认为单例对象是内存泄漏。程序员接下来还得再次去确认到底是不是内存泄漏 ,增加了程序员的额外的工作。

那么如何实现单例对象的自动释放呢?

—— 看到自动就应该想到当对象被销毁时,析构函数会被自动调用。

方式一:利用另一个对象的生命周期管理资源

利用对象的生命周期管理资源——析构函数(在析构函数中会执行delete _p),当对象被销毁时会自动调用。

要注意:

(1)如果还手动调用了Singleton类的destroy函数,会导致double free问题,所以可以删掉destroy函数,将回收堆上的单例对象的工作完全交给AutoRelease对象

如果手动调用destory会将_p指向的空间释放掉,而__p还不是空指针,还会指向那一块空间,当程序结束,系统自动调用autorelease 的析构函数,就会出现double free的情况

(2)不能用多个AutoRelease对象托管同一个堆上的单例对象。这样也会出现double free的情况

对个对象管理一块空间,原理也是差不多。

class AutoRelease{
public:AutoRelease(Singleton * p): _p(p){ cout << "AutoRelease(Singleton*)" << endl; }~AutoRelease(){cout << "~AutoRelease()" << endl;if(_p){delete _p;_p = nullptr;}}
private:Singleton * _p;
};void test0(){AutoRelease ar(Singleton::getInstance());Singleton::getInstance()->print();
}

方式二:嵌套类 + 静态对象(重点)

AutoRelease类对象_ar是Singleton类的对象成员,创建Singleton对象,就会自动创建一个AutoRelease对象(静态区),它的成员函数可以直接访问 _pInstance

class Singleton
{class AutoRelease{public:AutoRelease(){}~AutoRelease(){if(_pInstance){delete _pInstance;_pInstance = nullptr;}}};//...
private://...int _ix;int _iy;static Singleton * _pInstance;static AutoRelease _ar;
};
Singleton* Singleton::_pInstance = nullptr;
//使用AutoReleas类的无参构造对_ar进行初始化
Singleton::AutoRelease Singleton::_ar;void test1(){Singleton::getInstance()->print();Singleton::getInstance()->init(10,80);Singleton::getInstance()->print();
}

程序结束时会自动销毁全局静态区上的_ar,调用AutoRelease的析构函数,在这个析构函数执行delete _pInstance的语句,这样又会调用Singleton的析构函数,再调用operator delete,回收掉堆上的单例对象。

我们利用嵌套类实现了一个比较完美的方案,不用担心手动调用了destroy函数。如果手动调用也不会出现double free的问题,因为有if判断。

方式三:atexit + destroy

很多时候我们需要在程序退出的时候做一些诸如释放资源的操作,但程序退出的方式有很多种,比如main()函数运行结束、在程序的某个地方用exit()结束程序、用户通过Ctrl+C操作来终止程序等等,因此需要有一种与程序退出方式无关的方法来进行程序退出时的必要处理。

方法就是用atexit函数来注册程序正常终止时要被调用的函数(C/C++通用)。

如果注册了多个函数,先注册的后执行。

class Singleton
{
public:static Singleton * getInstance(){if(_pInstance == nullptr){atexit(destroy);  _pInstance = new Singleton(1,2);}return _pInstance;}//...
};

atexit注册了destroy函数,相当于有了一次必然会进行的destroy(程序结束时),即使手动调用了destroy,因为安全回收的机制,也不会有问题。

但是还遗留了一个问题,就是以上几种方式都无法解决多线程安全问题。以方式三为例,当多个线程同时进入if语句时,会造成单例对象被创建出多个,但是最终只有一个地址值会由_pInstance指针保存,因此造成内存泄漏。

可以使用饿汉式解决,但同时也可能带来内存压力(即使不用单例对象,也会被创建)

//对于_pInstance的初始化有两种方式//饱汉式(懒汉式)—— 懒加载,不使用到该对象,就不会创建
Singleton* Singleton::_pInstance = nullptr; //饿汉式 —— 最开始就创建(即使不使用这个单例对象)
Singleton* Singleton::_pInstance = getInstance();

饿汉式可以确保getInstance函数的第一次调用一定是在_pInstance的初始化时,之后再调用getInstance函数的时候,都不会进入if分支创建出对象。

同时,还有一个要考虑的问题——如果多线程环境下手动调用了destroy函数,那么又会让_pInstance变为空指针,之后再调用getInstance函数还是有可能造成内存泄露,

故而应该将destroy函数私有。

下面为测试代码,可以自行测试

#include <iostream>
using std::cout;
using std::endl;class Singleton {
public://静态成员函数只允许访问静态成员因为,静态成员函数没有this指针找不到非静态成员static Singleton* getInstance(){//当多个线程进去if语句//会造成Singleton对象被创建多个//最终只有一个对象的地址被_pInstance保存//其他对象就形成内存泄漏if (_pInstance == nullptr){//第三种方法//先进行注册,在程序结束会自动调用destoryatexit(destory);_pInstance = new Singleton(1, 2);}return _pInstance;}void init(int x, int y){_ix = x;_iy = y;}void print() const {cout << "(" << _ix<< "," << _iy<< ")" << endl;}friend class AutoRelease;
private://如果是多线程环境,手动调用destory会使_pInstance 成为空指针,再次getInstance还是会造成内存泄漏,所以将destory//设置为私有,destory不能随意调用// //如果是单线程就不会有这种问题static void destory(){if (_pInstance){delete _pInstance;_pInstance = nullptr;cout << ">> delete heap" << endl;}}//单例模式将拷贝构造和赋值运算符重载函数删除掉Singleton(const Singleton& rhs) = delete;Singleton & operator= (const Singleton& rhs) = delete;//将构造函数设置为私有Singleton(int x, int y):_ix(x), _iy(y){cout << "Singleton(int , int)" << endl;}//将析构函数设置为私有~Singleton(){cout << "~Singleton()" << endl;}int _ix;int _iy;static Singleton* _pInstance;
};
//饱汉式(懒汉式)——懒加载
Singleton* Singleton::_pInstance = nullptr;//饿汉式
//开始就将单例对象创建出来,即使程序中不使用这个单例对象
Singleton* Singleton::_pInstance = Singleton::getInstance();class AutoRelease {
public:AutoRelease(Singleton* p): _p(p){cout << "AutoRelease(Singleton*)" << endl;}~AutoRelease() {cout << "~AutoRelease()" << endl;if (_p) {//这里会报错是因为执行delete表达式会先调用析构函数回收对象申请的空间,然后再调用operator delete库函数回收本对象的空间//要想访问私有成员,必须声明为友元delete _p;_p = nullptr;}}
private:Singleton* _p;
};void test0() {AutoRelease ar(Singleton::getInstance());Singleton::getInstance()->print();//下面两种情况都会double free//Singleton::destory();//AutoRelease ar2(Singleton::getInstance());
}
int main()
{test0();return 0;
}

方式四:atexit + pthread_once

Linux平台可以使用的方法(能够保证创建单例对象时的多线程安全)

pthread_once函数可以确保初始化代码只会执行一次。

传给pthread_once函数的第一个参数比较特殊,形式固定;第二个参数需要是一个静态函数指针,pthread_once可以确保这个函数只会执行一次。

class Singleton
{
public:static Singleton * getInstance(){pthread_once(&_once,init_r);return _pInstance;}void init(int x,int y){_ix = x;_iy = y;}void print(){cout << "(" << this->_ix<< "," << this->_iy<< ")" << endl;}
private:static void init_r(){_pInstance = new Singleton(1,2);atexit(destroy);}static void destroy(){if(_pInstance){delete _pInstance;_pInstance = nullptr;}}Singleton() = default;//C++11Singleton(int x,int y): _ix(x), _iy(y){cout << "Singleton(int,int)" << endl;}~Singleton(){cout << "~Singleton()" << endl;}Singleton(const Singleton & rhs) = delete;Singleton & operator=(const Singleton & rhs) = delete;
private:int _ix;int _iy;static Singleton * _pInstance;static pthread_once_t _once;
};
Singleton * Singleton::_pInstance = nullptr;
pthread_once_t Singleton::_once = PTHREAD_ONCE_INIT;

注意:

(1)如果手动调用init_r创建对象,没有通过getInstance创建对象,实际上绕开了pthread_once的控制,必然造成内存泄露 —— 需要将init_r私有

(2)如果手动调用了destroy函数,之后再使用getInstance来尝试创建对象,因为pthread_once的控制效果,不会再执行init_r函数,所以无法再创建出单例对象。所以不能允许手动调用destroy函数。

同时因为会使用atexit注册destroy函数实现资源回收,所以也不能将destroy删掉,应该将destroy私有,避免在类外手动调用。

下面为测试代码,可自行测试

#include <pthread.h>
#include <iostream>
using std::cout;
using std::endl;class Singleton {
public://静态成员函数只允许访问静态成员因为,静态成员函数没有this指针找不到非静态成员static Singleton* getInstance(){//当多个线程进去if语句//会造成Singleton对象被创建多个//最终只有一个对象的地址被_pInstance保存//其他对象就形成内存泄漏// if (_pInstance == nullptr)// {//     //第三种方法//     //先进行注册,在程序结束会自动调用destory//     atexit(destory);//     _pInstance = new Singleton(1, 2);// }//第一个参数传特定的参数,第二个参数传函数指针pthread_once( &once_control, init_r);return _pInstance;}void init(int x, int y){_ix = x;_iy = y;}void print() const {cout << "(" << _ix<< "," << _iy<< ")" << endl;}friend class AutoRelease;
private://如果将init_r设置为公有,我就可以使用域名多次调用它,每调用一次就会指向一个新的对象,前面的对象还没有销毁,缩回就会产生内存泄漏// 要将init_r设置为私有,这样只有类内可以调用,在类外不可以随意调用 static void init_r(){_pInstance = new Singleton(1, 2);//注册destory,程序结束自定调用destoryatexit(destory);}//如果将destory定义为公有,那么我可以调用,销毁单例对象,如果我想再次使用单例对象,就不能使用了,因为pthread_once会限制只能//创建一个单例对象,所以将destory设置为私有static void destory(){if (_pInstance){delete _pInstance;_pInstance = nullptr;cout << ">> delete heap" << endl;}}//单例模式将拷贝构造和赋值运算符重载函数删除掉Singleton(const Singleton& rhs) = delete;Singleton& operator= (const Singleton& rhs) = delete;//将构造函数设置为私有Singleton(int x, int y):_ix(x), _iy(y){cout << "Singleton(int , int)" << endl;}//将析构函数设置为私有~Singleton(){cout << "~Singleton()" << endl;}int _ix;int _iy;static Singleton* _pInstance;//因为上面为静态成员函数,所以这个参数也要初始化为静态参数static pthread_once_t once_control;
};
pthread_once_t Singleton::once_control = PTHREAD_ONCE_INIT;
//饱汉式(懒汉式)——懒加载
Singleton* Singleton::_pInstance = nullptr;//饿汉式
//开始就将单例对象创建出来,即使程序中不使用这个单例对象
Singleton* Singleton::_pInstance = Singleton::getInstance();class AutoRelease {
public:AutoRelease(Singleton* p): _p(p){cout << "AutoRelease(Singleton*)" << endl;}~AutoRelease() {cout << "~AutoRelease()" << endl;if (_p) {//这里会报错是因为执行delete表达式会先调用析构函数回收对象申请的空间,然后再调用operator delete库函数回收本对象的空间//要想访问私有成员,必须声明为友元delete _p;_p = nullptr;}}
private:Singleton* _p;
};void test0() {Singleton::getInstance()->init(1, 2);Singleton::getInstance()->print();// Singleton::destory();//下面两种情况都会double free//Singleton::destory();//AutoRelease ar2(Singleton::getInstance());
}
int main()
{test0();return 0;
}

相关文章:

  • Hearts of Iron IV 钢铁雄心 4 [DLC 解锁] [Windows SteamOS macOS]
  • 机器学习-入门-决策树(1)
  • 第17节:传统分类模型-随机森林与决策树
  • day10 python机器学习全流程实践
  • Azure Synapse Dedicated SQL pool企业权限管理
  • 数据库操作
  • 轻松实现CI/CD: 用Go编写的命令行工具简化Jenkins构建
  • Java练习8
  • 【AlphaFold2】Feature extraction:提取特征,为模型输入做准备|Datapipeline讲解
  • 激光扫描仪的用途及优势
  • Java常用注解通俗解释
  • 【计算机视觉】目标检测:深度解析YOLOv5:下一代实时目标检测框架实战指南
  • UniApp 小程序嵌套 H5 页面显示隐藏监听实践
  • CentOS NFS共享目录
  • 关于3D的一些基础知识
  • Objective-C Block 底层原理深度解析
  • WEBSTORM前端 —— 第2章:CSS —— 第4节:盒子模型
  • phpstudy修改Apache端口号
  • (开源)视频画面增强模型:Ev-DeblurVSR (可以解决视频画面不清晰的问题)
  • C++之类和对象:构造函数,析构函数,拷贝构造,赋值运算符重载
  • 坚持科技创新引领,赢得未来发展新优势
  • 李铁案二审驳回上诉,维持一审有期徒刑20年的判决
  • 金砖国家外长会晤发表主席声明,强调南方国家合作
  • 中国人保不再设监事会,国寿集团未再设置监事长职务
  • 今年一季度全国城镇新增就业308万人,就业形势保持总体稳定
  • 辽宁辽阳市白塔区一饭店发生火灾,事故已造成22人遇难3人受伤