博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++智能指针auto_ptr源码完全解析---以微软auto_ptr为例来探讨auto_ptr的用法
阅读量:4140 次
发布时间:2019-05-25

本文共 5862 字,大约阅读时间需要 19 分钟。

       对于C/C++程序员来说, 内存泄露是一个谈之色变的话题, 很多时候, 机器运行1天2天都是ok的, 但运行到一个星期后, 就卡得要死。 实际上, 很多时候是内存泄露造成的。 内存泄露很容易引入, 但是定位起来非常非常难, 在内存泄露初期, 通常没有异常症状, 但随着内存泄露的累积, 内存逐渐被啃光, 最终导致卡死或者死机。

 

       申请堆内存, 又没有正确释放, 就会导致内存泄露, 听起来好可怕啊, 那有没有什么办法可以解决这一难题呢? 有的! 人类的智慧还是很厉害的。 我么知道, 栈对象在离开其作用域的时候, 会自动调用析构函数, 所以, 可以考虑把某一栈对象与某一堆内存绑定,且在其析构函数中释放堆内存,  那么, 在该栈对象离开作用域时, 堆内存自动释放, 这就是智能指针(本质是栈对象)的原理,简直是妙招啊。  这个栈对象装得像指针一样, 所以我们称之为智能指针, 其实, 它不过就是个普通的栈对象而已。 

 

        在本文中, 我们来介绍智能指针中的一种------auto_ptr,  并从源码的角度来看看auto_ptr使用中存在的一些常见问题:

 

       我们先来看一段简单的程序:

 

#include 
#include
// 有auto_ptr这个类模板using namespace std;int main(){ auto_ptr
p(new int(100)); // p是个对象, 但重载了*运算符, 所以我说它很能装, 装得像个指针 cout << *p << endl; // 100 return 0;}

 

 

      C++是一门复杂的语言, 比这个更糟糕的是: 一些不合格的C++程序员正在用它。 我们看看上面的程序, 实际上, 编译器做了太多的手脚, 这也是C++复杂的一个原因。 我们展开memory文件, 看看其中关于auto_ptr的代码, 复制过来, 然后形成如下程序:

 

#include 
using namespace std;// 微软能把代码写成这样, 也是够风骚的 // TEMPLATE CLASS auto_ptrtemplate
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
p(new int(100)); // p是个对象, 但重载了*运算符, 所以我说它很能装, 装得像个指针 cout << *p << endl; // 100 return 0;}

       看了上面auto_ptr的源码, 正在喝水的我, 差点呛着了。 并想问: 微软, 你还能再风骚一点么疑问

 

 

       好吧, 我也懒得计较了。 于是, 对上面代码进行风格整理, 并作出详细的注释, 就算是剖析一下auto_ptr的源码吧:

 

#include 
using namespace std;// 简单类class A{public: void fun() { }};template
// 类模板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
& y) throw() : m_bIsOwner(y.m_bIsOwner), m_ptr(y.release()) { cout << "debug2" << endl; } // owner转移 auto_ptr
& operator=(const auto_ptr
& 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
*)this)->m_bIsOwner = false; return m_ptr; } private: bool m_bIsOwner; // 对象是否拥有为owner的标志 T *m_ptr; // 对象绑定的指针};int main(){ { cout << "------------------------------" << endl; // 用法错误, 因为构造函数中有explicit, 不允许类型转化 //auto_ptr
p = new int(10); } { cout << "------------------------------" << endl; // ok auto_ptr
p(new int(10)); } { cout << "------------------------------" << endl; // 下面代码有严重的运行期错误, 实际上是尝试delete栈上的内容 int a = 10; //auto_ptr
p(&a); } { cout << "------------------------------" << endl; auto_ptr
p(new int(10)); // 错误, p虽然"看似像"指针, 其本质是对象, delete p;是未定义行为 //delete p; } { cout << "------------------------------" << endl; int *q = new int(10); auto_ptr
p(q); // 错误, q释放一次, p释放一次, 重复释放啊 //delete q; } { cout << "------------------------------" << endl; auto_ptr
p0; // 有debug3的打印, 但没有debug4, 知道原因了吧 p0 = p0; } { cout << "------------------------------" << endl; auto_ptr
p0(new int(10)); // 注意, 这是初始化, 不是复制, 所以不会有debug3的打印 auto_ptr
p1 = p0; } { cout << "------------------------------" << endl; auto_ptr
p0(new int(10)); auto_ptr
p1; // 注意, 这才是赋值, 所有有debug3, debug4, debug5, debug7, debug9, debug10的打印 // 为什么没有debug6呢? 因为当前对象p1还不是owner p1 = p0; } { cout << "------------------------------" << endl; auto_ptr
p0(new int(10)); auto_ptr
p1(new int(20)); // 有debug6的打印, 因为当先释放p1绑定的对象, 否则内存又泄露了啊 p1 = p0; } { cout << "------------------------------" << endl; auto_ptr
p0(new int(10)); // 把owner转给p1 auto_ptr
p1(p0); // 终于见到你了, debug8 p0 = p1; } { cout << "------------------------------" << endl; auto_ptr
p(new int(10)); // 见到你了, debug13 cout << *p << endl; } { cout << "------------------------------" << endl; auto_ptr
p(new A()); // 终于见到你了, debug15 p->fun(); } { cout << "------------------------------" << endl; auto_ptr
p0(new int(10)); auto_ptr
p1(p0); auto_ptr
p2(p1); // 实际上, p3才是最后的winner, 才是最后的owner, 所以释放堆的重任在p3身上 auto_ptr
p3(p2); } { cout << "------------------------------" << endl; // oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q; int *q = new int[3]; auto_ptr
p(q); } { cout << "------------------------------" << endl; // oh, my god, 内存泄露, 本来要delete [] q; 现在析构函数只执行delete q; int *q = new int[3]; auto_ptr
p(q); // 已经说过, 下面语句会造成内存重复释放 //delete q; } // 最后说明一下, auto_ptr不适合做容器的元素, 这一点我们以后会再次讨论到 return 0;}

    

       好了, 不多说auto_ptr了, 一切尽在代码中。

 

 

转载地址:http://ougvi.baihongyu.com/

你可能感兴趣的文章
python 变量作用域问题(经典坑)
查看>>
pytorch
查看>>
pytorch(三)
查看>>
ubuntu相关
查看>>
C++ 调用json
查看>>
nano中设置脚本开机自启动
查看>>
动态库调动态库
查看>>
Kubernetes集群搭建之CNI-Flanneld部署篇
查看>>
k8s web终端连接工具
查看>>
手绘VS码绘(一):静态图绘制(码绘使用P5.js)
查看>>
手绘VS码绘(二):动态图绘制(码绘使用Processing)
查看>>
基于P5.js的“绘画系统”
查看>>
《达芬奇的人生密码》观后感
查看>>
论文翻译:《一个包容性设计的具体例子:聋人导向可访问性》
查看>>
基于“分形”编写的交互应用
查看>>
《融入动画技术的交互应用》主题博文推荐
查看>>
链睿和家乐福合作推出下一代零售业隐私保护技术
查看>>
Unifrax宣布新建SiFAB™生产线
查看>>
艾默生纪念谷轮™在空调和制冷领域的百年创新成就
查看>>
NEXO代币持有者获得20,428,359.89美元股息
查看>>