C++智能指针auto_ptr源码完全解析---以微软auto_ptr为例来探讨auto_ptr的用法
来源:互联网 发布:数据库流程 编辑:程序博客网 时间:2024/05/16 10:06
对于C/C++程序员来说, 内存泄露是一个谈之色变的话题, 很多时候, 机器运行1天2天都是ok的, 但运行到一个星期后, 就卡得要死。 实际上, 很多时候是内存泄露造成的。 内存泄露很容易引入, 但是定位起来非常非常难, 在内存泄露初期, 通常没有异常症状, 但随着内存泄露的累积, 内存逐渐被啃光, 最终导致卡死或者死机。
申请堆内存, 又没有正确释放, 就会导致内存泄露, 听起来好可怕啊, 那有没有什么办法可以解决这一难题呢? 有的! 人类的智慧还是很厉害的。 我么知道, 栈对象在离开其作用域的时候, 会自动调用析构函数, 所以, 可以考虑把某一栈对象与某一堆内存绑定,且在其析构函数中释放堆内存, 那么, 在该栈对象离开作用域时, 堆内存自动释放, 这就是智能指针(本质是栈对象)的原理,简直是妙招啊。 这个栈对象装得像指针一样, 所以我们称之为智能指针, 其实, 它不过就是个普通的栈对象而已。
在本文中, 我们来介绍智能指针中的一种------auto_ptr, 并从源码的角度来看看auto_ptr使用中存在的一些常见问题:
我们先来看一段简单的程序:
#include <iostream>#include <memory> // 有auto_ptr这个类模板using namespace std;int main(){auto_ptr<int> p(new int(100));// p是个对象, 但重载了*运算符, 所以我说它很能装, 装得像个指针cout << *p << endl; // 100return 0;}
#include <iostream>using namespace std;// 微软能把代码写成这样, 也是够风骚的// TEMPLATE CLASS auto_ptrtemplate<class _Ty>class auto_ptr {public:typedef _Ty element_type;explicit auto_ptr(_Ty *_P = 0) _THROW0(): _Owns(_P != 0), _Ptr(_P) {}auto_ptr(const auto_ptr<_Ty>& _Y) _THROW0(): _Owns(_Y._Owns), _Ptr(_Y.release()) {}auto_ptr<_Ty>& operator=(const auto_ptr<_Ty>& _Y) _THROW0(){if (this != &_Y){if (_Ptr != _Y.get()){if (_Owns)delete _Ptr;_Owns = _Y._Owns; }else if (_Y._Owns)_Owns = true;_Ptr = _Y.release(); }return (*this); }~auto_ptr(){if (_Owns)delete _Ptr; }_Ty& operator*() const _THROW0(){return (*get()); }_Ty *operator->() const _THROW0(){return (get()); }_Ty *get() const _THROW0(){return (_Ptr); }_Ty *release() const _THROW0(){((auto_ptr<_Ty> *)this)->_Owns = false;return (_Ptr); }private:bool _Owns;_Ty *_Ptr;};int main(){auto_ptr<int> p(new int(100));// p是个对象, 但重载了*运算符, 所以我说它很能装, 装得像个指针cout << *p << endl; // 100return 0;}看了上面auto_ptr的源码, 正在喝水的我, 差点呛着了。 并想问: 微软, 你还能再风骚一点么
好吧, 我也懒得计较了。 于是, 对上面代码进行风格整理, 并作出详细的注释, 就算是剖析一下auto_ptr的源码吧:
#include <iostream>using namespace std;// 简单类class A{public:void fun(){}};template<class T>// 类模板class auto_ptr {public:// explicit构造函数, 禁止类型转化explicit auto_ptr(T *p = 0) throw(): m_bIsOwner(p != 0), m_ptr(p) {cout << "debug1" << endl;}// owner转移auto_ptr(const auto_ptr<T>& y) throw(): m_bIsOwner(y.m_bIsOwner), m_ptr(y.release()) {cout << "debug2" << endl;}// owner转移auto_ptr<T>& operator=(const auto_ptr<T>& y) throw(){cout << "debug3" << endl;if (this != &y) // 当前对象不是y对象{cout << "debug4" << endl;if (m_ptr != y.get()) // 当前对象绑定的地址不是y对象绑定的地址{cout << "debug5" << endl;if (m_bIsOwner) // 如果当前对象已经绑定堆, 则要先释放{cout << "debug6" << endl;delete m_ptr;}cout << "debug7" << endl;m_bIsOwner = y.m_bIsOwner; // 转移owner}else if (y.m_bIsOwner) // 当前对象与y绑定到同一块堆上, 且y是owner, 则把y的owner转移给当前对象{cout << "debug8" << endl;m_bIsOwner = true;}cout << "debug9" << endl;m_ptr = y.release(); // 让y不再是owner}cout << "debug10" << endl;return *this; // 返回当前对象的引用}// 析构函数~auto_ptr(){cout << "debug11" << endl;if (m_bIsOwner) // 只有拥有owner属性才释放堆, 这样避免重复释放{cout << "debug12" << endl;delete m_ptr; // 即使m_ptr是空指针也木有关系}}// 重载对象的*运算符, 使得对象"看起来"像指针, 可以执行*p操作T& operator*() const throw(){cout << "debug13" << endl;return *get(); }// 重载对象的->运算符T *operator->() const throw(){cout << "debug14" << endl;return get(); }// 获得对象绑定的地址T *get() const throw(){cout << "debug15" << endl;return m_ptr; }// 去掉对象的owner属性T *release() const throw(){cout << "debug16" << endl;((auto_ptr<T> *)this)->m_bIsOwner = false;return m_ptr; }private:bool m_bIsOwner; // 对象是否拥有为owner的标志T *m_ptr; // 对象绑定的指针};int main(){{cout << "------------------------------" << endl;// 用法错误, 因为构造函数中有explicit, 不允许类型转化//auto_ptr<int> p = new int(10);}{cout << "------------------------------" << endl;// okauto_ptr<int> p(new int(10));}{cout << "------------------------------" << endl;// 下面代码有严重的运行期错误, 实际上是尝试delete栈上的内容int a = 10;//auto_ptr<int> p(&a);}{cout << "------------------------------" << endl;auto_ptr<int> p(new int(10));// 错误, p虽然"看似像"指针, 其本质是对象, delete p;是未定义行为//delete p;}{cout << "------------------------------" << endl;int *q = new int(10);auto_ptr<int> p(q);// 错误, q释放一次, p释放一次, 重复释放啊//delete q;}{cout << "------------------------------" << endl;auto_ptr<int> p0;// 有debug3的打印, 但没有debug4, 知道原因了吧p0 = p0;}{cout << "------------------------------" << endl;auto_ptr<int> p0(new int(10));// 注意, 这是初始化, 不是复制, 所以不会有debug3的打印auto_ptr<int> p1 = p0; }{cout << "------------------------------" << endl;auto_ptr<int> p0(new int(10));auto_ptr<int> p1; // 注意, 这才是赋值, 所有有debug3, debug4, debug5, debug7, debug9, debug10的打印// 为什么没有debug6呢? 因为当前对象p1还不是ownerp1 = p0;}{cout << "------------------------------" << endl;auto_ptr<int> p0(new int(10));auto_ptr<int> p1(new int(20));// 有debug6的打印, 因为当先释放p1绑定的对象, 否则内存又泄露了啊p1 = p0;}{cout << "------------------------------" << endl;auto_ptr<int> p0(new int(10));// 把owner转给p1auto_ptr<int> p1(p0);// 终于见到你了, debug8p0 = p1;}{cout << "------------------------------" << endl;auto_ptr<int> p(new int(10));// 见到你了, debug13cout << *p << endl;}{cout << "------------------------------" << endl;auto_ptr<A> p(new A());// 终于见到你了, debug15p->fun();}{cout << "------------------------------" << endl;auto_ptr<int> p0(new int(10));auto_ptr<int> p1(p0);auto_ptr<int> p2(p1);// 实际上, p3才是最后的winner, 才是最后的owner, 所以释放堆的重任在p3身上auto_ptr<int> p3(p2);}{cout << "------------------------------" << endl;// oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q;int *q = new int[3];auto_ptr<int> p(q);}{cout << "------------------------------" << endl;// oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q;int *q = new int[3];auto_ptr<int> p(q);// 已经说过, 下面语句会造成内存重复释放//delete q;}// 最后说明一下, auto_ptr不适合做容器的元素, 这一点我们以后会再次讨论到return 0;}
好了, 不多说auto_ptr了, 一切尽在代码中。
0 0
- C++智能指针auto_ptr源码完全解析---以微软auto_ptr为例来探讨auto_ptr的用法
- 智能指针 auto_ptr 源码解析
- C++:auto_ptr智能指针的用法
- auto_ptr智能指针的用法
- 智能指针auto_ptr源码
- 智能指针auto_ptr解析
- 【C++】智能指针auto_ptr
- 基于auto_ptr源码的智能指针实现
- C++ auto_ptr智能指针的用法
- C++ auto_ptr 智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- C++ auto_ptr智能指针的用法
- 单例模式的常见应用场景
- JVM内存模型及垃圾收集策略解析
- 论测试在软件体系的地位
- HDU 5207 Greatest Greatest Common Divisor
- ASP.NET中使用JSON方便实现前台与后台的数据交换
- C++智能指针auto_ptr源码完全解析---以微软auto_ptr为例来探讨auto_ptr的用法
- 重写
- HTML5表单验证的例子
- PHP时间函数date()详解
- HDU 2121 Ice_cream’s world II (不定根最小树形图)
- 数组copy方法
- JAVA MD5加密的两种方法
- 华为在线测试题[基础篇]--求小球落地5次后所经历的路程和第5次反弹的高度
- Eclips中的快捷键