最近在看《C++标准程序库》,功力尚浅,看着有些磕绊,不过着实收获不小,不过看到auto_ptr实现里关于auto_ptr_ref的时候,对这个类不甚了解,并且联想到之前看《STL源码解析》时候,也有瞥见过这个代理类,是时候一并解决一下。
auto_ptr的特性
auto_ptr建立在“拥有权”这个概念之上,一个对象只能被一个auto_ptr所拥有,因此与一般类不同,auto_ptr的拷贝构造和赋值符重载的参数是 非const引用(一般类用const的原因是出于对右值传递的需要),因为在拷贝或者赋值的时候,需要修改传递的原auto_ptr的成员变量,才能完成“拥有权”的转移。
这样就会出现以下问题,假如需要定义这样一个auto_ptr:
/*用一个auto_ptr临时对象初始化ptr_1,会调用拷贝构造*/
class A;
auto_ptr<A> ptr_1(auto_ptr<A>(new A()));
那么由于auto_ptr<A>(new A())是右值,而右值是不能传递给非const引用,所以将无法匹配拷贝构造函数。
template <class T>
class my_auto_ptr
{
public:
my_auto_ptr(T *t)
{
pt = t;
cout<<"in constructed func !"<<endl;
}
my_auto_ptr(my_auto_ptr& tmp)
{
cout<<"in copy constructed fun !"<<endl;
}
private:
T *pt;
};
int main(int argc, char const *argv[])
{
my_auto_ptr<int> q(my_auto_ptr<int>(new int(10)));
return 0;
}
/*编译不过,如果把拷贝构造函数参数前加上const,则会调用拷贝构造函数进行*/
/*ps.g++编译可能需要加上-fno-elide_constructors选项*/
/*否则编译器会进行RVO优化,导致输出没有调用拷贝构造,导致产生和拷贝构造函数不相关的错觉,其实不然*/
auto_ptr_ref
那么对于这个临时对象需要怎么才能传递呢?拷贝构造函数是不能去动,那么是否可以写出这样的构造函数:
auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
/*临时对象是auto_ptr类型,这样子就可以传递了*/
/*但是这样显然编译不过的,会造成构造函数循环调用*/
为什么会构造函数循环调用
说到底构造函数也是函数,对于上述声明pass-by-value(因为是临时对象,pass-by-reference只能给const引用),需要传递外部对象,显然调用函数时候会首先构造一个临时对象,如果没有拷贝构造函数的话,这种情况如果定义类似A(A a)的构造函数,假设外部对象为a1,内部为a2,以此类推。那么整个构造过程就变成:A a1(A())->A a2(A())->(此时构造a2发现匹配构造函数,需要临时对象a3)->A a3(A())……显然,递归调用而且不会停止。所以如果有定义此种构造函数,编译器在编译阶段就制止了。所以一般这种需要传递对象进入一个调用函数,都是会调用拷贝构造函数,这也是拷贝构造函数的使命吧(严肃脸,纯属个人臆断23333333)
小插曲:面对百度解答的过程中,发现有个帖子提了构造函数调用构造函数的循环调用问题,在此写一下我自己的见解。
原帖地址http://bbs.csdn.net/topics/320261343
class Test
{
public:
Test(int i){m_i = i;}
Test(){Test(m_i);} /*在此处的Test(m_i)并不是调用构造函数,此种写法表示定义一个Test对象m_i,也就是等同于Test m_i*/
void SetValue (int j)
{
m_i = j;
}
int GetValue()
{
return m_i;
}
private:
int m_i;
};
int main()
{
Test b;//设断点
b.SetValue(10);
int value = b.GetValue();
cout<<value<<endl;
return 0;
}
在默认构造函数中,Test(m_i)其实没有匹配带参构造函数,在这里这种写法,因为m_i前面没有declare过,,其实等同于Test m_i,所以就一直循环调用不带参的构造函数。
可以写个例子验证一下:
class A
{
public:
A()
{
cout<<"in constructed func !"<<endl;
}
A(int i)
{
cout<<"in Ai constructed fun !"<<endl;
}
~A()
{
cout<<"in destructor func !"<<endl;
}
A(const A &tmp)
{
cout<<"in copy constructed fun !"<<endl;
}
};
int main(int argc, char const *argv[])
{
//my_auto_ptr<int> q(my_auto_ptr<int>(new int(10)));
//test(A());
int test = 1;
A(test); /*error: 'test' has a previous declaration as ‘int test’*/
A((int)test) /*OK*/
A(test_1) /*输出in constructed func !*/
/*in destructor func !*/
return 0;
}
很明显,对与A(test)这种调用,编译器认为是等同于A test,除非显示指出test是一个变量。所以对于帖子里的问题,解决办法就是:
Test(){Test(m_i);}//这个调用的是自身
Test(){Test((int)m_i);}//这样才是调用另一个
Test(){Test(3);}//这样也是调用另一个
Test(){Test(GetValue());}//这样也是另一个
Test(){Test(this->m_i);}
继续auto_ptr_ref,那么既然写不出A (A a)这种构造函数,那只能曲线救国。
template<typename _Tp1>
struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit
auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};
那么就可以写出如下构造函数:
auto_ptr(auto_ptr_ref<element_type> __ref) throw() //element_type就是auto_ptr的模板参数。
: _M_ptr(__ref._M_ptr) { }
通过ref的_M_ptr来构造auto_ptr的_M_ptr。
剩下的就是写一个auto_ptr到auto_ptr_ref的隐式转换了:
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw() { return auto_ptr_ref<_Tp1>(this->release()); }
/*将auto_ptr临时对象隐式转换为auto_ptr_ref*/
至此就可以写出auto_ptr<A> ptr_1(auto_ptr<A>(new A()));这样的定义了。
行文至此。
最后,总结一下,平时对构造函数析构函数拷贝构造函数其实流于表面,纸上得来终觉浅,还是要多躬行。
以上。