【C++智能指针】智能指针的发展和循环引用的原理和解决
创始人
2024-03-16 16:40:11
0

目录

1.RAIl(智能指针的雏形)

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

2.2auto_ptr(资源权转移,不建议使用)

         2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

         2.4shared_ptr

2.4.2循环引用的问题(重点)

 3.定制删除器


智能指针是拿来处理异常安全问题的

  • 下面这段代码如果抛异常了,那么p1就指向的空间就没有被释放,内存泄漏 
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void func()
{int* p1 = new int;cout << div() << endl; // 异常安全的问题delete p1;
}int main()
{try{func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

1.RAIl(智能指针的雏形)

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源,对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。(就是把原本资源托管给一个类对象)

优势:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效(声明周期在调用栈帧里面,声明周期结束自动调析构函数)
template
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}//生命周期结束,自动析构~SmartPtr(){cout << "delete:" << _ptr << endl;delete _ptr;}// 像迭代器一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void func()
{SmartPtr sp1(new int);SmartPtr sp2(new int);SmartPtr sp3(new int);*sp1 = 10;cout << *sp1 << endl;(*sp1)++;(*sp1)++;cout << *sp1 << endl;cout << div() << endl;
}int main()
{try{func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

 执行结果·:

解决了如果抛异常,在堆上开的空间得不到释放,导致的内存问题;

重载operator*和opertaor->,具有像指针一样的行为,可以正常访问

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

// RAII,把资源托管给一个类对象
// 用起来像指针一样
template
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << "delete:" << _ptr << endl;delete _ptr;}T& operator*(){return *_ptr;}// 像指针一样使用T* operator->(){return _ptr;}
private:T* _ptr;
};int main()
{//SmartPtr sp1(new int);//SmartPtr sp2(sp1);SmartPtr sp1(new int);SmartPtr sp2(new int);sp2 = sp1;return 0;
}

执行结果

  •  默认生成的拷贝构造,赋值重载都是浅拷贝,同一块空间被析构两次,程序奔溃

不能深拷贝的原因 

 2.2auto_ptr(资源权转移,不建议使用)

//auto_ptr
namespace LDJ
{templateclass auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr& sp):_ptr(sp._ptr)//:_ptr(nullptr){// 管理权转移sp._ptr = nullptr;//swap(_ptr, sp._ptr);}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}// 结论:auto_ptr是一个失败设计
int main()
{LDJ::auto_ptr sp1(new int);LDJ::auto_ptr sp2(sp1); // 管理权转移// sp1悬空*sp2 = 10;cout << *sp1 << endl;cout << *sp2 << endl;return 0;
}

执行结果

  • sp1悬空了; 

2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

  • unique_ptr的实现原理:简单粗暴的防拷贝
namespace LDJ
{template>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr& sp) = delete;unique_ptr& operator=(const unique_ptr& sp) = delete;private:T* _ptr;};
}

2.4shared_ptr

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
namespace LDJ
{templateclass shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)){}void AddRef(){++*(_pRefCount);}void Release(){if (--(*_pRefCount) == 0 && _ptr){delete _ptr;delete _pRefCount;}}shared_ptr(const shared_ptr& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pRefCount;}
}

2.4.1operator=需要处理一下

shared_ptr& operator=(const shared_ptr& sp){//if (this != &sp)if (this->_pRefCount != sp._pRefCount){Release();_ptr = sp._ptr;_pRefCount = sp._pRefCount;AddRef();return *this;}}

2.4.2循环引用的问题(重点)

struct Node
{~Node(){cout << "~Node" << endl;}int val;std::shared_ptr _next;std::shared_ptr _prev;
};
int main()
{std::shared_ptr sp1(new Node);std::shared_ptr sp2(new Node);sp1->_next = sp2;sp2->_prev = sp1;return 0;
}

执行结果:如果被析构了会打印两行~Node,然而并没有,说明sp1和sp2都没有被释放

 

循环引用的具体原因: 

解决循环引用weak_ptr

  • weak_ptr不是常规的智能指针,不增加计数支持访问,所以也不支持RAII 

struct Node
{~Node(){cout << "~Node" << endl;}int val;std::weak_ptr _next;std::weak_ptr _prev;
};
int main()
{std::shared_ptr sp1(new Node);std::shared_ptr sp2(new Node);cout << "sp1Count:" << sp1.use_count() << endl;cout << "sp2Count:" << sp2.use_count() << endl;sp1->_next = sp2;sp2->_prev = sp1;cout << "sp1Count:" << sp1.use_count() << endl;cout << "sp2Count:" << sp2.use_count() << endl;return 0;
}

 执行结果:前后sp1和sp2的计数都为1;所以可以证明weak_ptr不增加引用计数且解决了循环引用的问题;

 3.定制删除器

那么如果资源不是new出来的呢?比如:new[]、malloc、fopen,delete就不够用了

template
struct DeleteArray
{void operator()(const T* ptr){delete[] ptr;}
};
struct DeleteFile
{void operator()(FILE* ptr){fclose(ptr);}
};unique_ptr up1(new A);unique_ptr> up2(new A[10]);unique_ptr up3(fopen("test.txt", "w"));

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...