这几天开始拜读侯捷先生和孟岩先生的译作《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 <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的拷贝构造函数和赋值操作符,与一般类的做法不太相同。我们可以看看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;
}
一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移。
如果auto_ptr的拷贝构造函数和赋值操作符的参数是auto_ptr const &,那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。
右值与const &假设我们想写出下面的代码:
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))); //使用临时对象进行赋值
}
#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 <memory>
using namespace std;
auto_ptr<int> f(){
return auto_ptr<int>(new int(3)); //这里其实也使用临时对象进行拷贝构造
}
传说当年C++标准委员会的好多国家,因为这个问题都想把auto_ptr从标准库中剔除。好在Bill Gibbons和Greg Colvin创造性地提出了auto_ptr_ref,解决了这一问题,世界清静了。
auto_ptr_ref之原理
很显然,下面的构造函数,是可以接收auto_ptr临时对象的。
: _M_ptr(__ref._M_ptr) { }
operator auto_ptr_ref<_Tp1>() throw()
{ return auto_ptr_ref<_Tp1>(this->release()); }
using namespace std;
int main(int argc, char **argv) {
auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //调用auto_ptr_ref版本的构造函数
}
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 <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“引用”的语义,这一点可以从auto_ptr_ref的注释看出:
* A wrapper class to provide auto_ptr with reference semantics.
* For example, an auto_ptr can be assigned (or constructed from)
* the result of a function which returns an auto_ptr by value.
*
* All the auto_ptr_ref stuff should happen behind the scenes.
*/
template<typename _Tp1>
struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit
auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};
这里列出auto_ptr_ref相关的函数,共参考:
: _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());
}
注:
关于左值和右值
L = Location 可寻址
R = Read 可读