STL中的智能指针(Smart Pointer)及其源码剖析: std::auto_ptr
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 的声明
//(until C++17)
//(deprecated since C++11)
template<class T> class auto_ptr;
template<> class auto_ptr<void>;
2. auto_ptr 的构造函数
//deprecated...
explicit auto_ptr(X* p = 0); (1)
auto_ptr(auto_ptr& r); (2)
template<class Y> (3)
auto_ptr<auto_ptr<Y>& r);
template<class Y> (4)
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的析构函数: 销毁管理的对象。
~auto_ptr(); // deprecated
4. 拷贝赋值函数
//deprecated
auto_ptr& operator=(auto_ptr& r); (1)
template<class Y> (2)
auto_ptr& operator=(auto_ptr<Y>& r);
auto_ptr& operator=(auto_ptr_ref m); (3)
auto_ptr
的拷贝赋值函数会让 left hand value 接管 right hand value 所管理的对象。
5. 隐式类型转换函数
//deprecated...
template<class Y> (1)
operator auto_ptr_ref<Y>();
template<class Y> (2)
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)
//deprecated...
T* get() const; (1)
T& operator*() const; (2)
T* operator->() const; (3)
void reset(T* p = 0); (4)
T* release(); (5)
(1) 返回该 *this
所管理对象的指针。
(2) 返回该 *this
所管理对象。
(3) 返回该 *this
所管理对象的指针。
(4) 让 *this
管理 p
所指向的对象,如果 *this
已有管理的对象,则先 delete
掉当前管理的对象。
(5) 移交出 *this
所管理对象的管理权。返回 *this
所管理对象的指针,并将 *this
内部的指针置为空。
7. 例子
代码
#include <iostream> #include <string> #include <memory> using namespace std; // 展示测试结果
template<class Ty>
void Test(auto_ptr<Ty>& showPtr, string name, string hint)
{
cout << hint; if(showPtr.get() == nullptr) cout << name << ".get() == nullptr" << endl;
else cout << "*" << name << ".get() == " << *showPtr.get() << endl;
} // for test...
class Base
{
public:
Base(double pi = 0.0) : m_pi(pi){
//...
}
virtual void ShowName() const
{
cout << "Base Object";
}
double m_pi;
};
class Derive : public Base
{
public:
virtual void ShowName() const
{
cout << "Derive Object";
}
}; ostream& operator<<(ostream& os, const Base& b)
{
b.ShowName();
return os;
}
ostream& operator<<(ostream& os, const Derive& b)
{
b.ShowName();
return os;
} int main()
{
// 构造函数...
// explicit auto_ptr(X* p = 0); (1)
auto_ptr<int> intPtr1;
Test(intPtr1, "intPtr1", "explicit auto_ptr(X* p = 0)...\n");
int* ptr = new int(2);
auto_ptr<int> intPtr2(ptr);
Test(intPtr2, "intPtr2", "");
Derive* dp = new Derive;
auto_ptr<Derive> dptr(dp);
Test(dptr, "dptr", "");
cout << endl;
// auto_ptr(auto_ptr& r); (2)
auto_ptr<int> intPtr3(intPtr2);
Test(intPtr3, "intPtr3", "auto_ptr(auto_ptr& r)...\n");
Test(intPtr2, "intPtr2", "intPtr2 失去了对 ptr 的控制权: ");
cout << endl;
// template<class Y> (3)
// auto_ptr<auto_ptr<Y>& r);
auto_ptr<Base> bPtr(dptr);
Test(bPtr, "bPtr", "template<class Y> auto_ptr<auto_ptr<Y>& r)...\n");
Test(dptr, "dptr", "dptr 失去了对 dp 的控制权: ");
cout << endl;
// template<class Y> (4)
// auto_ptr(auto_ptr_ref<Y> m);
auto_ptr_ref<string> ptrRef(new string("many strings"));
auto_ptr<string> strPtr(ptrRef);
Test(strPtr, "strPtr", "template<class Y> auto_ptr(auto_ptr_ref<Y> m)...\n");
cout << endl; // 拷贝赋值函数
// auto_ptr& operator=(auto_ptr& r); (1)
auto_ptr<int> intPtr4;
intPtr4 = intPtr3;
Test(intPtr4, "intPtr4", "auto_ptr& operator=(auto_ptr& r)...\n");
Test(intPtr3, "intPtr3", "intPtr3 失去了对 ptr 的控制权: ");
cout << endl;
// template<class Y> (2)
// auto_ptr& operator=(auto_ptr<Y>& r);
auto_ptr<Derive> derivePtr(new Derive);
Test(derivePtr, "derivePtr", "template<class Y> auto_ptr& operator=(auto_ptr<Y>& r)...\n");
auto_ptr<Base> basePtr;
basePtr = derivePtr;
Test(basePtr, "basePtr", "basePtr 获得了控制权......\n");
Test(derivePtr, "derivePtr", "derivePtr 失去了控制权...\n");
cout << endl;
// auto_ptr& operator=(auto_ptr_ref m); (3)
auto_ptr_ref<string> strPtrRef(new string("auto_ptr_ref strings"));
auto_ptr<string> strAutoPtr;
strAutoPtr = strPtrRef;
Test(strAutoPtr, "strAutoPtr", "auto_ptr& operator=(auto_ptr_ref m)...\n");
cout << endl; // 其他函数
// T* get() const; (1)
int* pAddr = new int(5);
cout << "pAddr = " << pAddr << endl;
auto_ptr<int> addr(pAddr);
cout << "addr.get() = " << addr.get() << endl;
cout << endl;
// T& operator*() const; (2)
cout << "*pAddr = " << *pAddr << endl;
cout << "*addr.get() = " << *addr.get() << endl;
cout << endl;
// T* operator->() const; (3)
Base* pBase = new Base(3.14159);
auto_ptr<Base> spBase(pBase);
cout << "pBase->m_pi = " << pBase->m_pi << endl;
cout << "spBase->m_pi = " << spBase->m_pi << endl;
cout << endl;
// void reset(T* p = 0); (4)
intPtr4.reset(new int(-1));
Test(intPtr4, "intPtr4", "void reset(T* p = 0)...\n");
// T* release(); (5)
intPtr4.release();
Test(intPtr4, "intPtr4", "T* release()...\n");
cout << endl; return 0;
}运行结果:
二. auto_ptr 源码剖析(源码出自 VS2015)
1. 辅助类 auto_ptr_ref
的源码
template<class _Ty>
struct auto_ptr_ref
{ // proxy reference for auto_ptr copying
explicit auto_ptr_ref(_Ty *_Right)
: _Ref(_Right)
{ // construct from generic pointer to auto_ptr ptr
}
_Ty *_Ref; // generic pointer to auto_ptr ptr
};
这个辅助类的源码比较简单, 没有什么说的。前面也分析过了,这个辅助类其实是为了帮助 auto_ptr
完成右值引用传参而设计的。
2. auto_ptr
构造函数的源码
typedef auto_ptr<_Ty> _Myt; // 管理类的类型
typedef _Ty element_type; // 被管理元素的类型
explicit auto_ptr(_Ty *_Ptr = 0) (1)
: _Myptr(_Ptr)
{ // construct from object pointer
}
auto_ptr(_Myt& _Right) (2)
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
template<class _Other> (3)
auto_ptr(auto_ptr<_Other>& _Right)
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right
}
auto_ptr(auto_ptr_ref<_Tp> Right) (4)
: _Myptr(Right._Ref) {}
其中, auto_ptr
的成员变量 _Ty *_Myptr
指向被它管理的对象。
(1) 从原始指针中获取控制权。注意,由源码可知, auto_ptr
并没有将原始指针的控制权剥夺(从实现来看, 也不能剥夺, 因为 Ptr
不是指针引用,无法更改原始指针的指向),原始指针仍然保有对其资源的控制权。但是,该资源的释放权实际上已经交给了 auto_ptr
对象。如:
int* ptr = new int(3);
auto_ptr<int> autoPtr(ptr);
//error: 在 autoPtr 生命期结束后会释放ptr指向的资源。
//如果在这里释放资源, 在 autoPtr 生命期结束后就会崩溃。
delete ptr;
(2) 从 auto_ptr
对象中夺取对资源的控制权。由源码可知, _Right
将不再保有对其资源的控制。注意,这里是左值引用参数,不能接收右值参数。
(3) 与 (2) 类似。是针对可转换为 _Ty*
类型的 _Other*
类型的构造函数。
(4) 这个构造函数的参数是 auto_ptr_ref<_Ty>
类型的。注意,它不是左值引用类型的参数,因此可以接收右值类型。这也是右值传参的必经之路。
3. auto_ptr
析构函数的源码
~auto_ptr()
{ // destroy the object
delete _Myptr;
}
4. auto_ptr
拷贝赋值函数的源码
_Myt& operator=(_Myt& _Right) (1)
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
template<class _Other> (2)
_Myt& operator=(auto_ptr<_Other>& _Right)
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
_Myt& operator=(auto_ptr_ref<_Ty> _Right) (3)
{ // assign compatible _Right._Ref (assume pointer)
_Ty *_Ptr = _Right._Ref;
_Right._Ref = 0; // release old
reset(_Ptr); // set new
return (*this);
}
这里涉及到解决“自我赋值”(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
隐式类型转换函数
template<class _Other> (1)
operator auto_ptr_ref<_Other>()
{ // convert to compatible auto_ptr_ref
_Other *_Cvtptr = _Myptr; // test implicit conversion
auto_ptr_ref<_Other> _Ans(_Cvtptr);
_Myptr = 0; // pass ownership to auto_ptr_ref
return (_Ans);
}
template<class _Other> (2)
operator auto_ptr<_Other>()
{ // convert to compatible auto_ptr
return (auto_ptr<_Other>(*this));
}
(1) auto_ptr
到 auto_ptr_ref
的隐式转换函数。 由源码可知,该隐式转换也会剥夺 *this
对资源的管理权。 这个转换虽然代码短小,但是能量巨大, 右值类型的 auto_ptr
作参数传递时,全靠这个转换函数来起到周转的作用。当然,现在新的C++标准有更好的方法来解决这个问题 —— 右值引用。
(2) 可转换的类型…
6. auto_ptr
其他函数
_Ty *get() const (1)
{// return wrapped pointer
return (_Myptr);
}
_Ty& operator*() const (2)
{ // return designated value
if (_Myptr == 0)
_DEBUG_ERROR("auto_ptr not dereferencable");
return (*get());
}
_Ty *operator->() const (3)
{ // return pointer to class object
if (_Myptr == 0)
_DEBUG_ERROR("auto_ptr not dereferencable");
return (get());
}
void reset(_Ty *_Ptr = 0) (4)
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
_Ty *release() (5)
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = _Myptr;
_Myptr = 0;
return (_Tmp);
}
(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 相关源码
五. 推荐阅读
- 关于右值: cppreference.com关于值类型的详细解读:lvalue,rvalue,xvalue,prvalue,glvalue
- 关于右值引用: C++11移动语义探讨——从临时对象到右值引用
STL中的智能指针(Smart Pointer)及其源码剖析: std::auto_ptr的更多相关文章
- c/c++ 标准库 智能指针( smart pointer ) 是啥玩意儿
标准库 智能指针( smart pointer ) 是啥玩意儿 一,为什么有智能指针??? c++程序员需要自己善后自己动态开辟的内存,一旦忘了释放,内存就泄露. 智能指针可以帮助程序员"自 ...
- (转)Delphi2009初体验 - 语言篇 - 智能指针(Smart Pointer)的实现
转载:http://www.cnblogs.com/felixYeou/archive/2008/08/27/1277250.html 快速导航 一. 回顾历史二. 智能指针简介三. Delphi中 ...
- 智能指针类模板(上)——STL中的智能指针
智能指针类模板智能指针本质上就是一个对象,它可以像原生指针那样来使用. 智能指针的意义-现代C++开发库中最重要的类模板之一-C++中自动内存管理的主要手段-能够在很大程度上避开内存相关的问题 1.内 ...
- 转载:STL四种智能指针
转载至:https://blog.csdn.net/K346K346/article/details/81478223 STL一共给我们提供了四种智能指针: auto_ptr.unique_ptr.s ...
- C++中的智能指针类模板
1,智能指针本质上是一个对象,这个对象可以像原生的指针一样使用,因为智能指 针相关的类通过重载的技术将指针相关的操作符都进行了重载,所以智能指针对象可以像原生指针一样操作,今天学习智能指针类模板,通过 ...
- OSG中的智能指针
在OpenSceneGraph中,智能指针(Smart pointer)的概念指的是一种类的模板,它针对某一特定类型的对象(即Referenced类及其派生类)构建,提供了自己的管理模式,以避免因为用 ...
- 标准库中的智能指针shared_ptr
智能指针的出现是为了能够更加方便的解决动态内存的管理问题.注:曾经记得有本书上说可以通过vector来实现动态分配的内存的自动管理,但是经过试验,在gcc4.8.5下是不行的.这个是容易理解的,vec ...
- C++ 中的智能指针-基础
简介 在现代 C++ 编程中,标准库包含了智能指针(Smart pointers). 智能指针用来确保程序不会出现内存和资源的泄漏,并且是"异常安全"(exception-safe ...
- RPCZ中的智能指针单例
RPCZ中的智能指针单例 (金庆的专栏) 智能指针单例应用于 RPCZ 库以实现库的自动初始化与自动清理. RPCZ: RPC implementation for Protocol Buffers ...
- Boost中的智能指针(转)
这篇文章主要介绍 boost中的智能指针的使用.(转自:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html) 内存管理是一 ...
随机推荐
- Spring Security(4)
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 前面的方法中,除了login()方法能成功,另外两个都失败,并不是因为代码问题,而是Spring Security默认是通过Web页面来实现页面 ...
- .net如何优雅的使用EFCore
EFCore是微软官方的一款ORM框架,主要是用于实体和数据库对象之间的操作.功能非常强大,在老版本的时候叫做EF,后来.net core问世,EFCore也随之问世. 本文我们将用一个控制台项目Ho ...
- PHP日期加减计算
PHP 标准的日期格式 date("Y-m-d H:i:s"); PHP 简单的日期加减计算 <?php date_default_timezone_set('PRC'); ...
- day11 枚举类enum & 单例模式 & 异常以及抛出
day11 枚举enum 用enum关键字定义枚举类 特点 1.用enum关键字定义枚举类 2.枚举类默认继承java.lang.Enum类 3.枚举类的构造方法只能使用private修饰,省略则默认 ...
- Type Script 在流程设计器的落地实践
流程设计器项目介绍 从事过BPM行业的大佬必然对流程建模工具非常熟悉,做为WFMC三大体系结构模型中的核心模块,它是工作流的能力模型,其他模块都围绕工作流定义来构建. 成熟的建模工具通过可视化的操作界 ...
- keras小点记录
Keras学习小点记录 1.axis(轴) (1)解释 参考链接:https://www.zhihu.com/question/58993137 (2)测试 参考链接:http://keras-cn. ...
- linux安装Erlang和Rabbitmq以及安装问题解决
安装环境: Alibaba Cloud Linux 安装erlang命令: rpm --import https://packages.erlang-solutions.com/rpm/erlang_ ...
- 简易博客页面小项目 html css
项目预览 代码 html: <!DOCTYPE html> <html lang="en"> <head> <meta charset=& ...
- jmeter json提取器提取某个属性的所有值
json 提取器各字段说明: Variable names:保存的变量名,后面使用${Variable names}引用 JSON Path expressions:调试通过的json path表达 ...
- 2022NewStarCTF新生赛一些比较有意思的题目wp
Misc_蚁剑流量分析 Pcap的文件可以直接使用工具 编辑器打开目录,一个一个看,可以找到eval危险函数 看到n3wst4r,直接使用linux正则匹配,找出相关内容 Url解码,了解一下蚁剑流量 ...