STL中的智能指针(Smart Pointer)及其源码剖析: std::auto_ptr

  • auto_ptr 是STL中的智能指针家族的成员之一, 它管理由 new expression 获得的对象,在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。
  • auto_ptr 的拷贝构造函数和拷贝赋值会改变 right hand value,并且拷贝的副本不会等于原始的、被拷贝的那个 auto_ptr 对象的值。(实际上,auto_ptr 的拷贝构造函数和拷贝赋值函数会让 left hand value 接管 right hand value 所管理的对象。)
  • 由于不一样的拷贝语义, auto_ptr 不适用于标准容器, 因此,更建议使用std::unique_ptr

一. auto_ptr 的使用

1. auto_ptr 的声明

  1. //(until C++17)
  2. //(deprecated since C++11)
  3. template<class T> class auto_ptr;
  4. template<> class auto_ptr<void>;

2. auto_ptr 的构造函数

  1. //deprecated...
  2. explicit auto_ptr(X* p = 0); (1)
  3. auto_ptr(auto_ptr& r); (2)
  4. template<class Y> (3)
  5. auto_ptr<auto_ptr<Y>& r);
  6. template<class Y> (4)
  7. auto_ptr(auto_ptr_ref<Y> m);

(1) 构造 auto_ptr 对象, 让它管理 p 指向的对象。
(2) 构造 auto_ptr 对象,让它接管 r 管理的对象。实际上新的 auto_ptr 对象是靠 r.release() 函数获得管理权的。因此,r 失去了管理权。
(3) 这个构造函数和 (2) 类似, 主要针对能隐式转换为 T* 类型的 Y*
(4) 构造 auto_ptr 对象, 让它接管 auto_ptr_ref<Y> 类型的 m 管理的对象。而m 是通过 p.release()auto_ptr 对象 p 中获取管理权的。

Q: what is auto_ptr_ref, what it achieves and how it achieves it ?

A: It is rather confusing. Basically, auto_ptr_ref exists because the auto_ptr copy constructor isn’t really a copy constructor in the standard sense of the word.

Copy constructors typically have a signature that looks like this:

X(const X &b);
The auto_ptr copy constructor has a signature that looks like this:

X(X &b)
This is because auto_ptr needs to modify the object being copied from in order to set its pointer to 0 to facilitate the ownership semantics of auto_ptr.

Sometimes, temporaries cannot match a copy constructor that doesn’t declare its argument const. This is where auto_ptr_ref comes in. The compiler won’t be able to call the non-const version of the copy constructor, but it can call the conversion operator. The conversion operator creates an auto_ptr_ref object that’s just sort of a temporary holder for the pointer. The auto_ptr constructor or operator = is called with the auto_ptr_ref argument.

If you notice, the conversion operator in auto_ptr that automatically converts to an auto_ptr_ref does a release on the source auto_ptr, just like the copy constructor does.

It’s kind of a weird little dance that happens behind the scenes because auto_ptr modifies the thing being copied from.

简单地总结: auto_ptr_ref 主要解决用右值来构造 auto_ptr 的情况。 因为, auto_ptr(auto_ptr& r) 构造函数只能以左值引用做参数。当右值来构造 auto_ptr_ref 的时候,实际上实现过程如下(这其实是移动语义的早期实现版本):

3. auto_ptr的析构函数: 销毁管理的对象。

  1. ~auto_ptr(); // deprecated

4. 拷贝赋值函数

  1. //deprecated
  2. auto_ptr& operator=(auto_ptr& r); (1)
  3. template<class Y> (2)
  4. auto_ptr& operator=(auto_ptr<Y>& r);
  5. auto_ptr& operator=(auto_ptr_ref m); (3)

auto_ptr 的拷贝赋值函数会让 left hand value 接管 right hand value 所管理的对象。

5. 隐式类型转换函数

  1. //deprecated...
  2. template<class Y> (1)
  3. operator auto_ptr_ref<Y>();
  4. template<class Y> (2)
  5. operator auto_ptr<Y>();

(1) 将该对象隐式转换为 auto_ptr_ref<Y> 类型。
(2) 将该对象隐式转换为 auto_ptr<Y> 类型。

6. 其他函数(auto_ptr::get, auto_ptr::operator*、auto_ptr::operator->, auto_ptr::reset, auto_ptr::release)

  1. //deprecated...
  2. T* get() const; (1)
  3. T& operator*() const; (2)
  4. T* operator->() const; (3)
  5. void reset(T* p = 0); (4)
  6. T* release(); (5)

(1) 返回该 *this 所管理对象的指针。
(2) 返回该 *this 所管理对象。
(3) 返回该 *this 所管理对象的指针。
(4) 让 *this 管理 p 所指向的对象,如果 *this 已有管理的对象,则先 delete 掉当前管理的对象。
(5) 移交出 *this 所管理对象的管理权。返回 *this 所管理对象的指针,并将 *this 内部的指针置为空。

7. 例子

  • 代码


    1. #include <iostream>
    2. #include <string>
    3. #include <memory>
    4. using namespace std;
    5. // 展示测试结果
    6. template<class Ty>
    7. void Test(auto_ptr<Ty>& showPtr, string name, string hint)
    8. {
    9. cout << hint;
    10. if(showPtr.get() == nullptr) cout << name << ".get() == nullptr" << endl;
    11. else cout << "*" << name << ".get() == " << *showPtr.get() << endl;
    12. }
    13. // for test...
    14. class Base
    15. {
    16. public:
    17. Base(double pi = 0.0) : m_pi(pi){
    18. //...
    19. }
    20. virtual void ShowName() const
    21. {
    22. cout << "Base Object";
    23. }
    24. double m_pi;
    25. };
    26. class Derive : public Base
    27. {
    28. public:
    29. virtual void ShowName() const
    30. {
    31. cout << "Derive Object";
    32. }
    33. };
    34. ostream& operator<<(ostream& os, const Base& b)
    35. {
    36. b.ShowName();
    37. return os;
    38. }
    39. ostream& operator<<(ostream& os, const Derive& b)
    40. {
    41. b.ShowName();
    42. return os;
    43. }
    44. int main()
    45. {
    46. // 构造函数...
    47. // explicit auto_ptr(X* p = 0); (1)
    48. auto_ptr<int> intPtr1;
    49. Test(intPtr1, "intPtr1", "explicit auto_ptr(X* p = 0)...\n");
    50. int* ptr = new int(2);
    51. auto_ptr<int> intPtr2(ptr);
    52. Test(intPtr2, "intPtr2", "");
    53. Derive* dp = new Derive;
    54. auto_ptr<Derive> dptr(dp);
    55. Test(dptr, "dptr", "");
    56. cout << endl;
    57. // auto_ptr(auto_ptr& r); (2)
    58. auto_ptr<int> intPtr3(intPtr2);
    59. Test(intPtr3, "intPtr3", "auto_ptr(auto_ptr& r)...\n");
    60. Test(intPtr2, "intPtr2", "intPtr2 失去了对 ptr 的控制权: ");
    61. cout << endl;
    62. // template<class Y> (3)
    63. // auto_ptr<auto_ptr<Y>& r);
    64. auto_ptr<Base> bPtr(dptr);
    65. Test(bPtr, "bPtr", "template<class Y> auto_ptr<auto_ptr<Y>& r)...\n");
    66. Test(dptr, "dptr", "dptr 失去了对 dp 的控制权: ");
    67. cout << endl;
    68. // template<class Y> (4)
    69. // auto_ptr(auto_ptr_ref<Y> m);
    70. auto_ptr_ref<string> ptrRef(new string("many strings"));
    71. auto_ptr<string> strPtr(ptrRef);
    72. Test(strPtr, "strPtr", "template<class Y> auto_ptr(auto_ptr_ref<Y> m)...\n");
    73. cout << endl;
    74. // 拷贝赋值函数
    75. // auto_ptr& operator=(auto_ptr& r); (1)
    76. auto_ptr<int> intPtr4;
    77. intPtr4 = intPtr3;
    78. Test(intPtr4, "intPtr4", "auto_ptr& operator=(auto_ptr& r)...\n");
    79. Test(intPtr3, "intPtr3", "intPtr3 失去了对 ptr 的控制权: ");
    80. cout << endl;
    81. // template<class Y> (2)
    82. // auto_ptr& operator=(auto_ptr<Y>& r);
    83. auto_ptr<Derive> derivePtr(new Derive);
    84. Test(derivePtr, "derivePtr", "template<class Y> auto_ptr& operator=(auto_ptr<Y>& r)...\n");
    85. auto_ptr<Base> basePtr;
    86. basePtr = derivePtr;
    87. Test(basePtr, "basePtr", "basePtr 获得了控制权......\n");
    88. Test(derivePtr, "derivePtr", "derivePtr 失去了控制权...\n");
    89. cout << endl;
    90. // auto_ptr& operator=(auto_ptr_ref m); (3)
    91. auto_ptr_ref<string> strPtrRef(new string("auto_ptr_ref strings"));
    92. auto_ptr<string> strAutoPtr;
    93. strAutoPtr = strPtrRef;
    94. Test(strAutoPtr, "strAutoPtr", "auto_ptr& operator=(auto_ptr_ref m)...\n");
    95. cout << endl;
    96. // 其他函数
    97. // T* get() const; (1)
    98. int* pAddr = new int(5);
    99. cout << "pAddr = " << pAddr << endl;
    100. auto_ptr<int> addr(pAddr);
    101. cout << "addr.get() = " << addr.get() << endl;
    102. cout << endl;
    103. // T& operator*() const; (2)
    104. cout << "*pAddr = " << *pAddr << endl;
    105. cout << "*addr.get() = " << *addr.get() << endl;
    106. cout << endl;
    107. // T* operator->() const; (3)
    108. Base* pBase = new Base(3.14159);
    109. auto_ptr<Base> spBase(pBase);
    110. cout << "pBase->m_pi = " << pBase->m_pi << endl;
    111. cout << "spBase->m_pi = " << spBase->m_pi << endl;
    112. cout << endl;
    113. // void reset(T* p = 0); (4)
    114. intPtr4.reset(new int(-1));
    115. Test(intPtr4, "intPtr4", "void reset(T* p = 0)...\n");
    116. // T* release(); (5)
    117. intPtr4.release();
    118. Test(intPtr4, "intPtr4", "T* release()...\n");
    119. cout << endl;
    120. return 0;
    121. }
  • 运行结果:

二. auto_ptr 源码剖析(源码出自 VS2015)

1. 辅助类 auto_ptr_ref 的源码

  1. template<class _Ty>
  2. struct auto_ptr_ref
  3. { // proxy reference for auto_ptr copying
  4. explicit auto_ptr_ref(_Ty *_Right)
  5. : _Ref(_Right)
  6. { // construct from generic pointer to auto_ptr ptr
  7. }
  8. _Ty *_Ref; // generic pointer to auto_ptr ptr
  9. };

这个辅助类的源码比较简单, 没有什么说的。前面也分析过了,这个辅助类其实是为了帮助 auto_ptr 完成右值引用传参而设计的。

2. auto_ptr 构造函数的源码

  1. typedef auto_ptr<_Ty> _Myt; // 管理类的类型
  2. typedef _Ty element_type; // 被管理元素的类型
  3. explicit auto_ptr(_Ty *_Ptr = 0) (1)
  4. : _Myptr(_Ptr)
  5. { // construct from object pointer
  6. }
  7. auto_ptr(_Myt& _Right) (2)
  8. : _Myptr(_Right.release())
  9. { // construct by assuming pointer from _Right auto_ptr
  10. }
  11. template<class _Other> (3)
  12. auto_ptr(auto_ptr<_Other>& _Right)
  13. : _Myptr(_Right.release())
  14. { // construct by assuming pointer from _Right
  15. }
  16. auto_ptr(auto_ptr_ref<_Tp> Right) (4)
  17. : _Myptr(Right._Ref) {}

其中, auto_ptr 的成员变量 _Ty *_Myptr 指向被它管理的对象。
(1) 从原始指针中获取控制权。注意,由源码可知, auto_ptr 并没有将原始指针的控制权剥夺(从实现来看, 也不能剥夺, 因为 Ptr 不是指针引用,无法更改原始指针的指向),原始指针仍然保有对其资源的控制权。但是,该资源的释放权实际上已经交给了 auto_ptr 对象。如:

  1. int* ptr = new int(3);
  2. auto_ptr<int> autoPtr(ptr);
  3. //error: 在 autoPtr 生命期结束后会释放ptr指向的资源。
  4. //如果在这里释放资源, 在 autoPtr 生命期结束后就会崩溃。
  5. delete ptr;

(2) 从 auto_ptr 对象中夺取对资源的控制权。由源码可知, _Right 将不再保有对其资源的控制。注意,这里是左值引用参数,不能接收右值参数。
(3) 与 (2) 类似。是针对可转换为 _Ty* 类型的 _Other* 类型的构造函数。
(4) 这个构造函数的参数是 auto_ptr_ref<_Ty> 类型的。注意,它不是左值引用类型的参数,因此可以接收右值类型。这也是右值传参的必经之路。

3. auto_ptr 析构函数的源码

  1. ~auto_ptr()
  2. { // destroy the object
  3. delete _Myptr;
  4. }

4. auto_ptr 拷贝赋值函数的源码

  1. _Myt& operator=(_Myt& _Right) (1)
  2. { // assign compatible _Right (assume pointer)
  3. reset(_Right.release());
  4. return (*this);
  5. }
  6. template<class _Other> (2)
  7. _Myt& operator=(auto_ptr<_Other>& _Right)
  8. { // assign compatible _Right (assume pointer)
  9. reset(_Right.release());
  10. return (*this);
  11. }
  12. _Myt& operator=(auto_ptr_ref<_Ty> _Right) (3)
  13. { // assign compatible _Right._Ref (assume pointer)
  14. _Ty *_Ptr = _Right._Ref;
  15. _Right._Ref = 0; // release old
  16. reset(_Ptr); // set new
  17. return (*this);
  18. }

这里涉及到解决“自我赋值”(assignment to self)的问题,详情请参阅《Effective C++》 Item 11: Handle assignment to self in operator=。
(1) 将同类型的 _Right 管理的资源移交给 *this。其中,reset(_Right.release()) 先将 _Right 的资源以返回值的形式移交,然后设置给 *this, 这样就防止了“自我赋值”的时候出现问题。(如果 *this 就是 _Right, 那么执行完 _Right.release() 后, *this 管理的资源已经以返回值的形式移交出来作为参数,然后又 reset 给了自己)注意,这里是左值引用参数,不能接收右值参数。
(2) 与(1)类似。是针对可转换为 _Ty* 类型的 _Other* 类型的拷贝函数。
(3) 将一个 auto_ptr_ref 类型的变量赋值给 *this, 实际上是将资源的控制权移交给 *this。这同样是为了传右值参数而设计的。这个函数体内冗余的代码同样是为了防止 _Right._Ref 等于 _Myptr 的情况。

5. auto_ptr 隐式类型转换函数

  1. template<class _Other> (1)
  2. operator auto_ptr_ref<_Other>()
  3. { // convert to compatible auto_ptr_ref
  4. _Other *_Cvtptr = _Myptr; // test implicit conversion
  5. auto_ptr_ref<_Other> _Ans(_Cvtptr);
  6. _Myptr = 0; // pass ownership to auto_ptr_ref
  7. return (_Ans);
  8. }
  9. template<class _Other> (2)
  10. operator auto_ptr<_Other>()
  11. { // convert to compatible auto_ptr
  12. return (auto_ptr<_Other>(*this));
  13. }

(1) auto_ptrauto_ptr_ref 的隐式转换函数。 由源码可知,该隐式转换也会剥夺 *this 对资源的管理权。 这个转换虽然代码短小,但是能量巨大, 右值类型的 auto_ptr 作参数传递时,全靠这个转换函数来起到周转的作用。当然,现在新的C++标准有更好的方法来解决这个问题 —— 右值引用。
(2) 可转换的类型…

6. auto_ptr 其他函数

  1. _Ty *get() const (1)
  2. {// return wrapped pointer
  3. return (_Myptr);
  4. }
  5. _Ty& operator*() const (2)
  6. { // return designated value
  7. if (_Myptr == 0)
  8. _DEBUG_ERROR("auto_ptr not dereferencable");
  9. return (*get());
  10. }
  11. _Ty *operator->() const (3)
  12. { // return pointer to class object
  13. if (_Myptr == 0)
  14. _DEBUG_ERROR("auto_ptr not dereferencable");
  15. return (get());
  16. }
  17. void reset(_Ty *_Ptr = 0) (4)
  18. { // destroy designated object and store new pointer
  19. if (_Ptr != _Myptr)
  20. delete _Myptr;
  21. _Myptr = _Ptr;
  22. }
  23. _Ty *release() (5)
  24. { // return wrapped pointer and give up ownership
  25. _Ty *_Tmp = _Myptr;
  26. _Myptr = 0;
  27. return (_Tmp);
  28. }

(1) 获取 *this 所管理资源的指针, 这个没什么说的。
(2) 重载 operator*() 操作符,让 *this 有指针的行为。
(3) 重载 operator->() 操作符,让 *this 有指针的行为。
(4) 重新设置 *this 管理的资源, 当然在此之前要将 *this 管理的资源释放掉。类似于 operator= 的检查,如果_Ptr 指向的资源就是 *this 管理的资源,就忽略这个操作。否则会提前释放资源。
(5) *this 移交出管理权,并将资源的指针返回。因此需要先记录下资源的地址,然后将 *this 指向资源的指针置为空,最后返回资源的地址。

三. 总结

auto_ptr 用以 RAII(Resource Acquisition Is Initialization) 思想实现对资源的管理(详情可参考《Effective C++》Item 13: Use objects to manage resources)。但auto_ptr 属于该思想实现的早期版本,现在的标准库已经不推荐使用该工具了。但是,了解auto_ptr 的功能和实现还是有必要的,其一是,它相当于是其它更复杂智能指针的简化版本,源码简单,容易上手,对后面学习其它智能指针做铺垫; 其二是, 学习 auto_ptr 可以让那个我们对 RAII 思想有所领悟。

四. 参考文献

  • Scott Meyers 著, 侯捷译《Effective C++》
  • cppreference.com
  • VS2015 相关源码

五. 推荐阅读

STL中的智能指针(Smart Pointer)及其源码剖析: std::auto_ptr的更多相关文章

  1. c/c++ 标准库 智能指针( smart pointer ) 是啥玩意儿

    标准库 智能指针( smart pointer ) 是啥玩意儿 一,为什么有智能指针??? c++程序员需要自己善后自己动态开辟的内存,一旦忘了释放,内存就泄露. 智能指针可以帮助程序员"自 ...

  2. (转)Delphi2009初体验 - 语言篇 - 智能指针(Smart Pointer)的实现

     转载:http://www.cnblogs.com/felixYeou/archive/2008/08/27/1277250.html 快速导航 一. 回顾历史二. 智能指针简介三. Delphi中 ...

  3. 智能指针类模板(上)——STL中的智能指针

    智能指针类模板智能指针本质上就是一个对象,它可以像原生指针那样来使用. 智能指针的意义-现代C++开发库中最重要的类模板之一-C++中自动内存管理的主要手段-能够在很大程度上避开内存相关的问题 1.内 ...

  4. 转载:STL四种智能指针

    转载至:https://blog.csdn.net/K346K346/article/details/81478223 STL一共给我们提供了四种智能指针: auto_ptr.unique_ptr.s ...

  5. C++中的智能指针类模板

    1,智能指针本质上是一个对象,这个对象可以像原生的指针一样使用,因为智能指 针相关的类通过重载的技术将指针相关的操作符都进行了重载,所以智能指针对象可以像原生指针一样操作,今天学习智能指针类模板,通过 ...

  6. OSG中的智能指针

    在OpenSceneGraph中,智能指针(Smart pointer)的概念指的是一种类的模板,它针对某一特定类型的对象(即Referenced类及其派生类)构建,提供了自己的管理模式,以避免因为用 ...

  7. 标准库中的智能指针shared_ptr

    智能指针的出现是为了能够更加方便的解决动态内存的管理问题.注:曾经记得有本书上说可以通过vector来实现动态分配的内存的自动管理,但是经过试验,在gcc4.8.5下是不行的.这个是容易理解的,vec ...

  8. C++ 中的智能指针-基础

    简介 在现代 C++ 编程中,标准库包含了智能指针(Smart pointers). 智能指针用来确保程序不会出现内存和资源的泄漏,并且是"异常安全"(exception-safe ...

  9. RPCZ中的智能指针单例

    RPCZ中的智能指针单例 (金庆的专栏) 智能指针单例应用于 RPCZ 库以实现库的自动初始化与自动清理. RPCZ: RPC implementation for Protocol Buffers ...

  10. Boost中的智能指针(转)

    这篇文章主要介绍 boost中的智能指针的使用.(转自:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html) 内存管理是一 ...

随机推荐

  1. PDF、视频格式缩略图获取(pdf2img)

    PDF.视频格式缩略图获取(pdf2img) 获取pdf缩略图 导入依赖: <dependency> <groupId>org.apache.pdfbox</groupI ...

  2. from 表单非空验证以及多表单提交

    开发中我们常用到$('#formid').serialize()方法进行表单序列化提交,但也相应催生了表单的非空严重以及多表单提交. form html: <form id="form ...

  3. Python爬虫爬取彼岸网4K Picture

    深夜爬取4k图片 下载流程 定义page_text函数,对第一页地址发送get请求,因为页面数据在页面源代码都能查到,所以发送get 请求就ok!,注意:要进行编码格式设置,可以去源代码查看, 定义p ...

  4. MISC中的图片修改宽高问题

    在做CTF中MISC分类题目时,很常见的一个问题就是修改图片正确的宽与高 (此篇笔记中的内容以ctfshow中MISC入门分类为切入点,感兴趣的同学可以一边做一边有不会的看看,仅供参考,我是菜鸡) 曾 ...

  5. .NET 6 基于IDistributedCache实现Redis与MemoryCache的缓存帮助类

    本文通过IDistributedCache的接口方法,实现Redis与MemoryCache统一帮助类.只需要在配置文件中简单的配置一下,就可以实现Redis与MemoryCache的切换. 目录 I ...

  6. python中文件操作相关基础知识

    python中文件操作相关基础知识 文件的概念 1,文件的概念? 文件就是操作系统暴露给用户操作硬盘的快捷方式 , 当我们双击一个文件进行打开时,其实就是把硬盘中的数据加载到了内存中,然后展示给我们 ...

  7. Django框架模板语法传值-过滤器-标签-自定义过滤器,标签,inclusion_tag

    目录 一:模版语法传值 1.模板语法两个书写方式 2.模板语法 3.测试模板语法是否可以把python支持的基本数据类型传入到前端 4.index.html 5.django模板语法取值方式 二:过滤 ...

  8. python 定时发送邮件

    import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart f ...

  9. vscode问题:由于找不到ffmpag.dll文件,无法继续执行代码

    工作中发现VS code打不开了,显示如下:  解决方法: 一.打开Microsoft VS Code 文件夹,发现一部分文件被打包进了一个叫"_"的文件夹(第一个)  二.把该文 ...

  10. WCH以太网相关芯片资料总结

    网络产品线产品分类 1.接口控制类.CH395Q           http://www.wch.cn/search?t=all&q=395CH395LCH392F            h ...