auto_ptr:唯一拥有权
智能指针(智能指针是一个对象)保证,无论在何种情形下,只要自己被摧毁,就一定连带释放其所指资源。而由于智能型指针本身就是区域变量,所以无论是正常退出,还是异常退出,只要函数退出,它就一定会被摧毁。
只有auto_ptr可以拿来当做另一个auto_ptr 的初值,普通指针是不行的(因为没有一个自动类型转换,可以将普通指针转换成智能指针——根据一般指针生成一个auto_ptr的那个构造函数,被声明为explicit。explicit的用法参见相关内容)。
std::auto_ptr<ClassA> ptr; // create an auto_ptr
ptr = new ClassA; // Error
ptr = std::auto_ptr<ClassA>(new ClassA); // OK, delete old object and own new
牢记:auto_ptr的语义本身就包含了拥有权,所以如果你无意转交你的拥有权,就不要在参数列中使用auto_ptr,也不要以它作为返回值。原因如下:
(1)作为参数(实参,函数调用过程中传入)的auto_ptr会将拥有权转交给参数p(形参,函数定义中,假设为p)——“值传递”,而当函数退出时,会删除参数p所拥有的对象.
(2)以pass by reference传递的话,面对一个“透过by reference 而获得auto_ptr”的函数,你根本无法预知拥有权是否被转交.
解决方法(如果非要将auto_ptr当做参数传递的话):
(1)可以运用constant reference,避免了拥有权转交给参数p的问题;
(2)将auto_ptr定义为constant,如 “const std::auto_ptr
这一方案使得auto_ptr比显得更安全一些。事实上,C++标准程序库的所有容器都如此,如:
template <class T>
void container::insert (const T& value) // constant reference
{
...
x = value; //
...
};
总之,为了避免拥有权的意外转移,如果你的auto_ptr在整个生命周期内都不必改变其所指对象的拥有权,你可以使用const auto_ptr。
auto_ptr不满足STL容器对其元素的要求,因为在拷贝和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。拷贝和赋值之后,原本的auto_ptr会交出拥有权,而不是拷贝给新的auto_ptr(原auto_ptr交出拥有权,变成null指针了)。因此请绝对不要将auto_ptr作为标准容器的元素。
例外:我们不应该以任何形式传递auto_ptr,但当面对output操作符的时候,我们可以将auto_ptr以const reference的方式传递(只作简单输出行为,不会修改)。
在auto_ptr构造函数“explicit auto_ptr::auto_ptr(T* ptr) throw() ”中:
(1)构造完成后,*this成为ptr所指对象的唯一拥有者。不允许再有其他拥有者;
(2)如果ptr本身不是null指针,那就必须是个new返回值,因为auto_ptr析构函数会对其所拥有的对象自动调用delete;
(3)不能用new[] 所生成的array作为初值。因为auto_ptr是调用delete删除对象,而不是调用delete[]。
注意:auto_ptr的copy构造函数和assignment 函数被设计为转交auto_ptr所有权。
“auto_ptr::auto_ptr(auto_ptr& ap) throw()”,
“template
针对non-const values而设计的一个copy构造函数,生成一个auto_ptr,在入口处将ap所拥有的对象的拥有权夺取过来,ap变为null指针。这个操作改变了原对象。此函数有一个重载的member template,使得ap可通过型别自动转换,构造出合适的auto_ptr。例如,根据一个“派生类的对象”,构造出一个基类对象的auto_ptr.
auto_ptr最后一个内容涉及左值/右值,引进了auto_ptr_ref类别,使我们得以拷贝和赋值non-const auto_ptrs(包括临时对象),却不能拷贝和赋值const auto_ptrs。详细解释如下:
为什么需要auto_ptr_ref
auto_ptr的拥有权 :
C++常见的智能指针有std::auto_ptr、boost::shared_ptr、boost::scoped_ptr、boost::shared_array、boost::scoped_array等。auto_ptr只是其中一种而已。但是,为什么auto_ptr才有auto_ptr_ref ,而boost::shared_ptr却没有shared_ptr_ref呢?答案与auto_ptr的特性有关。auto_ptr强调对资源的拥有权 (ownership)。也就是说,auto_ptr是"它所指对象"的拥有者。而一个对象只能属于一个拥有者,严禁一物二主,否则就是重婚罪,意料外的灾难将随之而来。
为了保证auto_ptr的拥有权唯一,auto_ptr的拷贝构造函数和赋值操作符做了这样一件事情:移除另一个auto_ptr的拥有权 。为了说明拥有权的转移 ,请看下面的代码示例:
// ----------------------------------------------------------------------------
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char **argv){
auto_ptr<int> ptr1(new int(1));
auto_ptr<int> ptr2(ptr1); //ptr1的拥有权被转移到ptr2
auto_ptr<int> ptr3(NULL);
ptr3 = ptr2; //ptr2的拥有权被转移到ptr3
cout<<ptr1.get()<<endl; //结果为0
cout<<ptr2.get()<<endl; //结果为0
cout<<*ptr3<<endl; //结果为1
}
// --------------------------------------------------------------------------
auto_ptr的拷贝构造函数与赋值操作符
由于需要实现拥有权的转移,auto_ptr的拷贝构造函数和赋值操作符,与一般类的做法不太相同。我们可以看看MinGW5.1.6实现的auto_ptr源代码:
// --------------------------------------------------------------------------
/**
* @brief An %auto_ptr can be constructed from another %auto_ptr.
* @param a Another %auto_ptr of the same type.
*
* This object now @e owns the object previously owned by @a a,
* which has given up ownsership.
*/
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}
/**
* @brief %auto_ptr assignment operator.
* @param a Another %auto_ptr of the same type.
*
* This object now @e owns the object previously owned by @a a,
* which has given up ownsership. The object that this one @e
* used to own and track has been deleted.
*/
auto_ptr&
operator=(auto_ptr& __a) throw () {
reset(__a.release());
return *this;
}
可以看到,auto_ptr的拷贝构造函数、赋值操作符,它们的参数都是auto_ptr& (为了转交所有权,),而不是const auto_ptr &。
一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移。
如果auto_ptr的拷贝构造函数和赋值操作符的参数是const auto_ptr &, 那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。
右值与const &:
假设我们想写出下面的代码:
// --------------------------------------------------------------------------
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char **argv) {
auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //使用临时对象进行拷贝构造
auto_ptr<int> ptr2(NULL);
ptr2 = (auto_ptr<int>(new int(2))); //使用临时对象进行赋值
}
假设没有定义auto_ptr_ref类及相关的函数,那么这段代码将不能通过编译。主要的原因是,拷贝构造函数及赋值操作符的参数:auto_ptr
左值和右值:
左值可以出现在赋值语句的左边或右边。
右值只能出现在赋值语句的右边。(一般是临时变量、函数的非引用返回值,参见C++易混淆知识点整理 第2点)
例如 xy是个右值,编译表达式xy=10;则出现错误.
非const引用不能绑定右值
同理,下面的两段代码,也不会通过编译:
// --------------------------------------------------------------------------
#include <iostream>
#include <memory>
using namespace std;
auto_ptr<int> f();
int main(int argc, char **argv) {
auto_ptr<int> ptr3(f()); //使用临时对象进行拷贝构造
auto_ptr<int> ptr4(NULL);
ptr4 = f(); //使用临时对象进行赋值
}
// --------------------------------------------------------------------------
#include <iostream>
#include <memory>
using namespace std;
auto_ptr<int> f(){
return auto_ptr<int>(new int(3)); //这里其实也使用临时对象进行拷贝构造
}
普通类不会遇到这个问题,是因为他们的拷贝构造函数及赋值操作符(不管是用户定义还是编译器生成的版本),参数都是const &。
auto_ptr_ref之目的
传说当年C++标准委员会的好多国家,因为这个问题都想把auto_ptr从标准库中剔除。好在Bill Gibbons和Greg Colvin创造性地提出了auto_ptr_ref,解决了这一问题,世界清静了。
auto_ptr_ref之原理
很显然,下面的构造函数,是可以接收auto_ptr临时对象的。
// 传值,而不是传引用,可以接收临时对象
auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
但另一个问题也很显然:上述构造函数不能通过编译。如果能通过编译,就会陷入循环调用(为什么?谁看了之后理解的留下评论,谢谢)。我们稍作修改:
// --------------------------------------------------------------------------
auto_ptr(auto_ptr_ref<element_type> __ref) throw() //element_type就是auto_ptr的模板参数。
: _M_ptr(__ref._M_ptr) { }
该版本的构造函数,可以接收auto_ptr_ref的临时对象。如果auto_ptr可以隐式转换到auto_ptr_ref,那么我们就能够用auto_ptr临时对象来调用该构造函数。这个隐式转换不难实现:
// 类的类型转换运算符,在auto_ptr类里面实现,目的:将auto_ptr 隐式转换为auto_ptr_ref<_Tp1>类型 --------------------------------------------------------------------------
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw()
{ return auto_ptr_ref<_Tp1>(this->release()); }
附录1:SGI STL 中auto_ptr的实现:
至此,我们可以写出下面的代码,并可以通过编译:
// --------------------------------------------------------------------------
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char **argv) {
auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //由于是临时对象,所以不会调用到“auto_ptr(auto_ptr& __a)”,而是调用auto_ptr_ref版本的构造函数
}
同理,如果我们再提供下面的函数:
// --------------------------------------------------------------------------
auto_ptr&
operator=(auto_ptr_ref<element_type> __ref) throw()
{
if (__ref._M_ptr != this->get())
{
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return *this;
}
那么,下面的代码也可以通过编译:
// --------------------------------------------------------------------------
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char **argv) {
auto_ptr<int> ptr2(NULL);
ptr2 = (auto_ptr<int>(new int(2))); //调用auto_ptr_ref版本的赋值操作符
}
auto_ptr_ref之本质
本质上,auto_ptr_ref赋予了auto_ptr“引用”的语义 。
附录1:SGI STL 中auto_ptr的实现:
#ifndef __SGI_STL_MEMORY
#define __SGI_STL_MEMORY
#include <stl_algobase.h>
#include <stl_alloc.h>
#include <stl_construct.h>
#include <stl_tempbuf.h>
#include <stl_uninitialized.h>
#include <stl_raw_storage_iter.h>
__STL_BEGIN_NAMESPACE
#if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && \
defined(__STL_MEMBER_TEMPLATES)
template<class _Tp1> struct auto_ptr_ref { // 定义auto_ptr_ref类型 _Tp1* _M_ptr;
auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {}
};
#endif
template <class _Tp> class auto_ptr { // 定义auto_ptrprivate:
_Tp* _M_ptr;
public:
typedef _Tp element_type;
// 构造函数,原始指针类型参数
explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}
// 拷贝构造函数,接收非const引用类型参数 auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
#ifdef __STL_MEMBER_TEMPLATES
// 模板 拷贝构造函数 template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW
: _M_ptr(__a.release()) {}
#endif /* __STL_MEMBER_TEMPLATES */
auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
if (&__a != this) {
delete _M_ptr;
_M_ptr = __a.release();
}
return *this;
}
#ifdef __STL_MEMBER_TEMPLATES
template <class _Tp1>
auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
if (__a.get() != this->get()) {
delete _M_ptr;
_M_ptr = __a.release();
}
return *this;
}
#endif /* __STL_MEMBER_TEMPLATES */
~auto_ptr() __STL_NOTHROW { delete _M_ptr; }
_Tp& operator*() const __STL_NOTHROW {
return *_M_ptr;
}
_Tp* operator->() const __STL_NOTHROW {
return _M_ptr;
}
_Tp* get() const __STL_NOTHROW {
return _M_ptr;
}
_Tp* release() __STL_NOTHROW {
_Tp* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
void reset(_Tp* __p = 0) __STL_NOTHROW {
if (__p != _M_ptr) {
delete _M_ptr;
_M_ptr = __p;
}
}
// According to the C++ standard, these conversions are required. Most
// present-day compilers, however, do not enforce that requirement---and,
// in fact, most present-day compilers do not support the language
// features that these conversions rely on.
#if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && \
defined(__STL_MEMBER_TEMPLATES)
public:
// 接收auto_ptr_ref<_Tp>类型参数,或者接收auto_ptr临时对象类型参数(隐式转换成auto_ptr_ref<_Tp>类型) auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
: _M_ptr(__ref._M_ptr) {}
auto_ptr& operator=(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW {
if (__ref._M_ptr != this->get()) {
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return *this;
}
// 定义 auto_ptr类型转换成auto_ptr_ref类型的 类型转换运算符(可以实现隐式转换,因为没有使用关键字explicit) template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW
{ return auto_ptr_ref<_Tp1>(this->release()); }
template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW
{ return auto_ptr<_Tp1>(this->release()); }
#endif /* auto ptr conversions && member templates */
};
__STL_END_NAMESPACE
#endif /* __SGI_STL_MEMORY */
// Local Variables:
// mode:C++
// End:
原文链接: https://www.cnblogs.com/yyxt/p/5012569.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/225288
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!