为什么需要 auto_ptr_ref

 

转载自: http://jarfield.iteye.com/blog/746062

这几天开始拜读侯捷先生和孟岩先生的译作《C++标准程序库:自修教程与参考手册》 。两位先生确实译功上乘,读得很顺。但是读到P55页关于auto_ptr_ref的讨论,却百思不得其解:为什么需要引入auto_ptr_ref这个辅助类呢?

 

从书中描述来看,仿佛与拷贝构造函数右值类型转换 有关。于是,结合auto_ptr的源代码,google之、baidu之,找了一推资料,终于初步 搞清该问题。

 

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的拷贝构造函数和赋值操作符,与一般类的做法不太相同。我们可以看看MinGW 5.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& ,而不是auto_ptr const &

 

    一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移

 

    如果auto_ptr的拷贝构造函数和赋值操作符的参数是auto_ptr const & ,那么实参的拥有权将不能转移。因为转移拥有权需要修改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<int>(new int(1))和 auto_ptr<int>(new int(2)) 都是临时对象 。临时对象属于典型的右值 ,而非const &是不能指向右值的 (参见More Effective C++ ,Item 19)。auto_ptr的拷贝构造函数及赋值操作符的参数类型恰恰是auto_ptr&,明显 非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临时对象来调用该构造函数。这个隐式转换不难实现:

    1. template<typename _Tp1>   
    2.         operator auto_ptr_ref<_Tp1>() throw()   
    3.         { return auto_ptr_ref<_Tp1>(this->release()); }  

    至此,我们可以写出下面的代码,并可以通过编译:

    #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_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;
          }


    那么,下面的代码也可以通过编译:

    auto_ptr<int> ptr2(NULL);   

     ptr2 = (auto_ptr<int>(new int(2)));  //调用auto_ptr_ref版本的赋值操作符  

    auto_ptr_ref之本质

    本质上,auto_ptr_ref赋予了auto_ptr“引用”的语义,这一点可以从auto_ptr_ref的注释看出:

    auto_ptr_ref之代码

    这里列出auto_ptr_ref相关的函数,共参考:

    auto_ptr(auto_ptr_ref<element_type> __ref) throw()
    : _M_ptr(__ref._M_ptr) {}
    
    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;
    }
    
    template<typename _Tp1>
    operator auto_ptr_ref<_Tp1>() throw () {
    	return auto_ptr_ref<_Tp1> (this->release());
    }
    
    template<typename _Tp1>
    operator auto_ptr<_Tp1>() throw () {
    	return auto_ptr<_Tp1> (this->release());
    }



     

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值