目录
一,为什么需要智能指针
二,内存泄露的基本认识
1. 内存泄露分类
2. 常见的内存检测工具
3,如何避免内存泄露
三,智能指针的使用与原理
1. RAII思想
2. 智能指针
(1. unique_ptr
3. weak_ptr解决循环引用问题
4. 定制删除器(了解)
嗨!收到一张超美的风景图,愿你每天都能顺心!
一,为什么需要智能指针
假设我们调用func函数,我们会发现:div函数操作,如果抛异常,则p1, p2就不会释放导致内存泄露。
void Func (){// 1 、如果 p1 这里 new 抛异常会如何?// 2 、如果 p2 这里 new 抛异常会如何?// 3 、如果 div 调用这里又会抛异常会如何?int* p1 = new int ;int* p2 = new int ;cout << div () << endl ;delete p1 ;delete p2 ;}
二,内存泄露的基本认识
1. 内存泄露分类
2. 常见的内存检测工具
在linux下内存泄漏检测: Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客
在windows下使用第三方工具 : VS编程内存泄漏:VLD(Visual LeakDetector)内存泄露库_visual leak detector vs2020-CSDN博客
其他工具: 内存泄露检测工具比较 - 默默淡然 - 博客园 (cnblogs.com)
3,如何避免内存泄露
三,智能指针的使用与原理
1. RAII思想
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if(_ptr)delete _ptr;}private:T* _ptr;
};
int div(){int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;}
void Func()
{ShardPtr<int> sp1(new int);ShardPtr<int> sp2(new int);cout << div() << endl;
}
int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}
2. 智能指针
所以,基本的框架就有了:
// 功能像指针一样
template <class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return &(*_ptr);}
private:T* _ptr;
};
这只是初步的框架,我们知道这个SmartPtr类的拷贝构造是浅拷贝。如果两个该类对象指向同一个内容,在析构时将会析构两次,进而出现报错。换个方向,这个类类似我们学习的迭代器,但我们当时没有考虑析构的问题?
答:迭代器只是管理数据的机构,数据析构是数据本身的事情。
说到拷贝,我们要认识智能指针的发展史:
C++98的不好用,在C++11中引入了unique_ptr, shared_ptr&weak_ptr,他们两来自Boost库 。
Boost的准标准库(标准库就是C++编译器就支持的,准标准库需要外部引入源码库的),怎么理解Boost? 可以理解为标准库的体验服。
(1. unique_ptr
主旨:简单粗暴禁止拷贝。(auto_ptr栽在拷贝上)
方法一:将构造函数声明并放入private区中,这样外部无法定义访问。
方法二:强制禁用
(2. shared_ptr & weak_ptr
玩法:引用计数,并且支持拷贝
1. shared_ptr在其内部, 给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。2. 在 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象, 必须释放该资源;4. 如果不是0,就说明除了自己还有其他对象在使用该份资源, 不能释放该资源,否则其他对象就成野指针了。
下面是简单模拟实现的shared_ptr:
template <class T>
class share_ptr
{
public:share_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)),_mtx(new mutex){}~share_ptr(){destory();}void destory(){_mtx->lock();bool flag = false;if (--(*_pcount) == 0){cout << " delete :" << *_ptr << endl;delete _ptr;delete _pcount;flag = true;}_mtx->unlock();if (flag == true){delete _mtx;}}void AddCount(const share_ptr<T>& it){_mtx->lock();(*it._pcount)++;_mtx->unlock();}T& operator*(){return *_ptr;}T* operator->(){return &(*_ptr);}share_ptr(const share_ptr<T>& it):_ptr(it._ptr),_pcount(it._pcount),_mtx(it._mtx){AddCount(it);}share_ptr<T>& operator=(const share_ptr<T>& it){// 防止自己赋值自己导致数据丢失if (_ptr != it._ptr){destory();_ptr = it._ptr;_mtx = it._mtx;_pcount = it._pcount;AddCount(it); // 计数++return *this;}}
private:T* _ptr;int* _pcount;mutex* _mtx;
};
shared_ptr的循环引用问题
特征:互相使用shared_ptr指向对方, 且双方有第三者指向
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << " "; // 打印链接数
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << " ";
cout << node2.use_count() << endl;
return 0;}
3. weak_ptr解决循环引用问题
特征:
1. 不支持RAII思想
2. 像指针
3. 专门用来辅助shared_ptr,可以指向资源,但不管理资源,也不增加计数。
// 简化版本的weak_ptr实现
template<class T>
class weak_ptr
{
public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const share_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
4. 定制删除器(了解)
我们注意到,我们的析构函数底层都是delete,但对象是数组,文件指针,delete析构就不合适,所以我们的shared_ptr有了定制删除器的用法。
本质上就是类似仿函数,下面是几个使用案例:
template <class T>
struct DeleteArray
{void operator()(T* ptr){if (ptr != nullptr){delete[] ptr;}}
};
int main()
{// 自己写仿函数法shared_ptr<Date> pt1(new Date[10], DeleteArray<Date>());// 函数指针法这个过于简单的就不做演示// lambda法shared_ptr<Date> pt2(new Date[100], [](Date* ptr) { delete[] ptr; });// function对象法shared_ptr<Date> pt3(new Date[1000], function<void(Date*)> (DeleteArray<Date>()));// 文件管理shared_ptr<FILE> pt4(fopen("test.txt", "r"), [](FILE* ptr) { fclose(ptr); });cout << "endl";return 0;
}
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。