文章目录
C++智能指针
1、为什么需要智能指针
下面我们先分析一下下面这段程序有没有什么内存方面的问题?提示一下:注意分析Func函数中的问题。
int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; } 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; int *p1, *p2; p1 = new int; // p1抛异常直接退出就行了 try { p2 = new int; // p2抛异常的时候,p1已经申请好了,必须释放p1 cout << div() << endl; } catch (...) { // 这里捕获异常 需要p1释放 delete p1; cout << "unknow error" << endl; throw;// 交给上一层栈帧处理 } delete p1; delete p2; } int main() { try { Func(); } catch (exception &e) { cout << e.what() << endl; } catch (...) { cout << "unknow error" << endl; } return 0; }
我们发现,这样写的代码很难看,并且,如果申请的内存越多,那么try catch组合就越多,代码也就越不易阅读!
2、内存泄漏
2.1、什么是内存泄漏以及内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
我们来看以下代码:
void MemoryLeaks() { // 1.内存申请了忘记释放 int* p1 = (int*)malloc(sizeof(int)); int* p2 = new int; // 2.异常安全问题 int* p3 = new int[10]; Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放. delete[] p3; }
2.2、内存泄漏分类
C/C++程序中一般我们关心两种方面的内存泄漏:
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
2.3、内存泄漏检测
- 在linux下内存泄漏检测:linux下几款内存泄漏检测工具
- 在windows下使用第三方工具:VLD工具说明
- 其他工具:内存泄漏工具比较
2.4、如何避免内存泄漏
- 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
- 采用RAII思想或者智能指针来管理资源。
- 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
- 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
- 内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具
3、智能指针
3.1、RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效
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() { SmartPtr<int> sp1(new int); //这里就算出现异常,也不会存在内存释放问题,因为sp1出函数的时候会调用析构函数 SmartPtr<int> sp2(new int); cout << div() << endl; } int main() { try { Func(); } catch (const exception &e) { cout << e.what() << endl; } catch (...) { cout << "unknow error" << endl; } return 0; }
3.2、智能指针的原理
上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载,才可让其像指针一样去使用。
template<class T> class SmartPtr { public: SmartPtr(T *ptr = nullptr) : _ptr(ptr) { } ~SmartPtr() { if (_ptr) delete _ptr; } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; class Date { public: Date() = default; Date(int year, int month, int day) : _year(year), _month(month), _day(day) {} int &GetYear() { return _year; } int &GetMonth() { return _month; } int &GetDay() { return _day; } private: int _year; int _month; int _day; }; int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; }
总结智能指针的原理:
- RAII特性
- 重载operator*和operator->,具有像指针一样的行为。
3.3、auto_ptr
auto_ptr的使用及其问题
#include <iostream> #include <memory> using namespace std; // VS下才能使用,CLion不支持auto_ptr int main() { auto_ptr<int> ap1(new int(1)); auto_ptr<int> ap2(new int(2)); auto_ptr<int> ap3(ap2); // 这里ap3把ap2的资源转移了 *ap1 += 10; //*ap2 += 10;// 这里ap2已经置空了,不能访问,访问就报错 *ap3 += 10; return 0; }
我们自己模拟实现auto_ptr,以便于理解。
namespace xp { template<class T> class auto_ptr { public: auto_ptr(T *ptr = nullptr) : _ptr(ptr) { } auto_ptr(auto_ptr<T> &ap) { _ptr = ap._ptr; ap._ptr = nullptr; } auto_ptr<T> &operator=(auto_ptr<T> &ap) { if (_ptr != ap._ptr) { // 释放当前对象资源 if (_ptr) delete _ptr; _ptr = ap._ptr; ap._ptr = nullptr; } return *this; } ~auto_ptr() { if (_ptr) delete _ptr; } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; } int main() { xp::auto_ptr<int> ap1(new int(1)); xp::auto_ptr<int> ap2(new int(2)); *ap1 += 10; *ap2 += 10; xp::auto_ptr<int> ap3(ap2); // *ap2 += 1; ap3 = ap1; return 0; }
我们可以看到auto_ptr的问题是在进行拷贝构造和赋值的时候,会使得原指针置空,在进行拷贝构造和赋值之后不能再使用!
3.4、unique_ptr
C++11提供更靠谱的unique_ptr
unique_ptr简单粗暴,直接防拷贝(它的拷贝构造声明为delete就行)。
unique_ptr的使用及问题
// unique_str的使用 int main() { unique_ptr<int> ap1(new int(1)); unique_ptr<int> ap2(new int(2)); *ap1 += 10; *ap2 += 10; unique_ptr<int> ap3(ap2); // 不允许拷贝构造 ap3 = ap1; // 不允许赋值 return 0; }
unique_ptr的模拟实现
namespace xp { template<class T> class unique_ptr { public: unique_ptr(T *ptr = nullptr) : _ptr(ptr) { } unique_ptr(unique_ptr<T> &ap) = delete; unique_ptr<T> &operator=(unique_ptr<T> &ap) = delete; ~unique_ptr() { if (_ptr) delete _ptr; } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; } int main() { xp::unique_ptr<int> ap1(new int(1)); xp::unique_ptr<int> ap2(new int(2)); *ap1 += 10; *ap2 += 10; // xp::unique_ptr<int> ap3(ap2); // 不允许拷贝构造 // *ap2 += 1; // ap3 = ap1; // 不允许赋值 return 0; }
我们可以看到,unique_ptr不支持拷贝构造和赋值。
3.5、shared_ptr
C++11提供更靠谱的shared_ptr,shared_ptr支持拷贝构造和赋值
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:晚自习下课后,最后一个出教室的学生关灯。
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1。
- 如果减减后的引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
- 如果减减后的不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
shared_ptr的使用
// share_ptr 的使用 int main() { shared_ptr<int> sp1(new int(1)); shared_ptr<int> sp2(new int(3)); cout << sp2.use_count() << endl; *sp1 += 1; *sp2 += 1; shared_ptr<int> sp3(sp2); cout << sp2.use_count() << endl; sp1 = sp2; cout << sp2.use_count() << endl; return 0; }
shared_ptr的模拟实现
namespace xp { template<class T> class shared_ptr { public: shared_ptr(T *ptr = nullptr) : _ptr(ptr) { _pcount = new int(1); // 构造的时候引用计数设置为1 } shared_ptr(shared_ptr<T> &sp) { _ptr = sp._ptr;// 先指向同一块空间 _pcount = sp._pcount; // 再引用计数++ ++(*_pcount); } shared_ptr<T> &operator=(shared_ptr<T> &sp) { if (_ptr != sp._ptr) { // 当前指针指向的引用计数--,--后引用计数等于0就释放 release(); // sp指向的引用计数++ _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); } return *this; } void release() { if (--(*_pcount) == 0) { if (_ptr) { cout << "delete _ptr" << endl; delete _ptr; delete _pcount; } } } ~shared_ptr() { release(); } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } int use_count() { return *_pcount; } T *get() const { return _ptr; } private: T *_ptr; int *_pcount; }; // weak_ptr没有引用计数,相当于就是我们之前写的SmartPtr template<class T> class weak_ptr { public: weak_ptr(T *ptr) : _ptr(ptr) {} weak_ptr(shared_ptr<T> &wp) { _ptr = wp.get(); } weak_ptr<T> &operator=(shared_ptr<T> &wp) { _ptr = wp.get(); return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; } int main() { xp::shared_ptr<int> sp1(new int(1)); xp::shared_ptr<int> sp2(new int(3)); cout << sp2.use_count() << endl; *sp1 += 1; *sp2 += 1; xp::shared_ptr<int> sp3(sp2); cout << sp2.use_count() << endl; sp1 = sp2; cout << sp2.use_count() << endl; return 0; }
shared_ptr的循环引用问题
namespace xp { template<class T> class shared_ptr { public: shared_ptr(T *ptr = nullptr) : _ptr(ptr) { _pcount = new int(1); // 构造的时候引用计数设置为1 } shared_ptr(const shared_ptr<T> &sp) { _ptr = sp._ptr;// 先指向同一块空间 _pcount = sp._pcount; // 再引用计数++ ++(*_pcount); } shared_ptr<T> &operator=(const shared_ptr<T> &sp) { if (_ptr != sp._ptr) { // 当前指针指向的引用计数--,--后引用计数等于0就释放 release(); // sp指向的引用计数++ _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); } return *this; } void release() { if (--(*_pcount) == 0) { if (_ptr) { cout << "delete _ptr" << endl; delete _ptr; delete _pcount; } } } ~shared_ptr() { release(); } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } int use_count() { return *_pcount; } T *get() const { return _ptr; } private: T *_ptr; int *_pcount; }; // weak_ptr没有引用计数,相当于就是我们之前写的SmartPtr template<class T> class weak_ptr { public: weak_ptr(T *ptr) : _ptr(ptr) {} weak_ptr(shared_ptr<T> &wp) { _ptr = wp.get(); } weak_ptr<T> &operator=(shared_ptr<T> &wp) { _ptr = wp.get(); return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; } struct ListNode { int _data; xp::shared_ptr<ListNode> _prev; xp::shared_ptr<ListNode> _next; ListNode(int val = 0) : _data(val), _prev(nullptr), _next(nullptr) {} ~ListNode() { cout << "~ListNode()" << endl; } }; // shared_ptr的循环引用问题 int main() { xp::shared_ptr<ListNode> node1(new ListNode); xp::shared_ptr<ListNode> node2(new ListNode); cout << node1.use_count() << endl; cout << node2.use_count() << endl; node1->_next = node2; node2->_prev = node1; cout << node1.use_count() << endl; cout << node2.use_count() << endl; return 0; }
循环引用分析:
node1
和node2
两个智能指针对象指向两个结点,引用计数都为1
,出main
函数自动析构(delete,但是得看引用计数)。node1
的_next
指向node2
,node2
的引用计数+1
,node2
的_prev
指向node1
,node1
的引用计数+1
。node1
和node2
出main
函数析构,引用计数减到1
,但是_next
还指向下一个结点,_prev
还指向上一个结点。- 也就是说
_next
释放了,node2
就释放了,_prev
释放了,node1
就释放了。- 但是
_next
是属于node1
的成员,需要node1
释放_next
才释放,_prev
是属于node2
的成员,需要node2
释放_prev
才释放。- 这就是循环引用,都在等对方释放,就像死锁。
解决方案:
在结点使用引用计数的情况下,结点
_prev
和_next
指针不使用引用计数(使用weak_ptr)。namespace xp { template<class T> class shared_ptr { public: shared_ptr(T *ptr = nullptr) : _ptr(ptr) { _pcount = new int(1); // 构造的时候引用计数设置为1 } shared_ptr(const shared_ptr<T> &sp) { _ptr = sp._ptr;// 先指向同一块空间 _pcount = sp._pcount; // 再引用计数++ ++(*_pcount); } shared_ptr<T> &operator=(const shared_ptr<T> &sp) { if (_ptr != sp._ptr) { // 当前指针指向的引用计数--,--后引用计数等于0就释放 release(); // sp指向的引用计数++ _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); } return *this; } void release() { if (--(*_pcount) == 0) { if (_ptr) { cout << "delete _ptr" << endl; delete _ptr; delete _pcount; } } } ~shared_ptr() { release(); } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } int use_count() { return *_pcount; } T *get() const { return _ptr; } private: T *_ptr; int *_pcount; }; // weak_ptr没有引用计数,相当于就是我们之前写的SmartPtr template<class T> class weak_ptr { public: weak_ptr(T *ptr) : _ptr(ptr) {} weak_ptr(shared_ptr<T> &wp) { _ptr = wp.get(); } weak_ptr<T> &operator=(shared_ptr<T> &wp) { _ptr = wp.get(); return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; } struct ListNode { int _data; xp::weak_ptr<ListNode> _prev; xp::weak_ptr<ListNode> _next; ListNode(int val = 0) : _data(val), _prev(nullptr), _next(nullptr) {} ~ListNode() { cout << "~ListNode()" << endl; } }; // shared_ptr的循环引用问题解决 int main() { xp::shared_ptr<ListNode> node1(new ListNode); xp::shared_ptr<ListNode> node2(new ListNode); cout << node1.use_count() << endl; cout << node2.use_count() << endl; node1->_next = node2; node2->_prev = node1; cout << node1.use_count() << endl; cout << node2.use_count() << endl; return 0; }
3.5.1、删除器
如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题。
删除器的使用:
// 仿函数删除器 template<class T> struct FreeFunc { void operator()(T *ptr) { cout << "free :" << ptr << endl; free(ptr); } }; template<class T> struct DeleteArray { void operator()(T *ptr) { cout << "delete[] :" << ptr << endl; delete[] ptr; } }; // shared_ptr的删除器使用 int main() { FreeFunc<int> freeFunc; shared_ptr<int> sp1((int *) malloc(4), freeFunc); DeleteArray<int> deleteArray; shared_ptr<int> sp2(new int[10], deleteArray); // lambda 表达式删除器 shared_ptr<int> sp3(new int, [](int *ptr) { delete ptr; }); shared_ptr<FILE> sp4(fopen("hello.txt", "w"), [](FILE *pf) { fclose(pf); }); return 0; }
完善shared_ptr的删除器功能:
namespace xp { template<class T> class shared_ptr { public: template<class D> shared_ptr(T *ptr, D del) : _ptr(ptr), _del(del) { _pcount = new int(1); // 构造的时候引用计数设置为1 } shared_ptr(T *ptr = nullptr) : _ptr(ptr) { _pcount = new int(1); // 构造的时候引用计数设置为1 } shared_ptr(const shared_ptr<T> &sp) { _ptr = sp._ptr;// 先指向同一块空间 _pcount = sp._pcount; // 再引用计数++ ++(*_pcount); } shared_ptr<T> &operator=(const shared_ptr<T> &sp) { if (_ptr != sp._ptr) { // 当前指针指向的引用计数--,--后引用计数等于0就释放 release(); // sp指向的引用计数++ _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); } return *this; } void release() { if (--(*_pcount) == 0) { if (_ptr) { cout << "delete _ptr" << endl; // delete _ptr; _del(_ptr); delete _pcount; } } } ~shared_ptr() { release(); } // 重载*,->使其获得指针的行为 T &operator*() { return *_ptr; } T *operator->() { return _ptr; } int use_count() { return *_pcount; } T *get() const { return _ptr; } private: function<void(T *)> _del = [](T *ptr) { delete ptr; }; T *_ptr; int *_pcount; }; // weak_ptr没有引用计数,相当于就是我们之前写的SmartPtr template<class T> class weak_ptr { public: weak_ptr(T *ptr) : _ptr(ptr) {} weak_ptr(shared_ptr<T> &wp) { _ptr = wp.get(); } weak_ptr<T> &operator=(shared_ptr<T> &wp) { _ptr = wp.get(); return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; } // 仿函数删除器 template<class T> struct FreeFunc { void operator()(T *ptr) { cout << "free :" << ptr << endl; free(ptr); } }; template<class T> struct DeleteArray { void operator()(T *ptr) { cout << "delete[] :" << ptr << endl; delete[] ptr; } }; int main() { FreeFunc<int> freeFunc; xp::shared_ptr<int> sp1((int *) malloc(4), freeFunc); DeleteArray<int> deleteArray; xp::shared_ptr<int> sp2(new int[10], deleteArray); // lambda 表达式删除器 xp::shared_ptr<int> sp3(new int, [](int *ptr) { cout << "sp3 delete" << endl; delete ptr; }); xp::shared_ptr<FILE> sp4(fopen("hello.txt", "w"), [](FILE *pf) { cout << "sp4 delete" << endl; fclose(pf); }); return 0; }
4、C++11和boost中智能指针的关系
- C++ 98 中产生了第一个智能指针auto_ptr.
- C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
- C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
- C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
这里还存在一个shared_ptr的线程安全问题,等后面学完Linux线程再来。
OKOK,C++智能指针就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。