先说点题外话。
在C++中,有两个行为:
- 如果某函数接受的参数不是期待的类型,编译器会尝试进行隐式的转换。将传入参数进行适配,使得函数调用能够成功
- 这样的隐式的转换只能进行一次。编译器不可能先将TypeA隐式转换为TypeB,接着又把TypeB隐式转换为TypeC
下面一段代码验证了这两个行为:
1 struct A 2 { 3 int _m; 4 A():_m(0){}; 5 }; 6 7 struct B 8 { 9 A _m; 10 int _alias; 11 B(A a):_m(a),_alias(0){}; 12 }; 13 14 struct C 15 { 16 B _m; 17 C(B b):_m(b){}; 18 }; 19 20 struct D 21 { 22 C _m; 23 D(C c):_m(c){}; 24 }; 25 26 A a; 27 B b(a); 28 C c1(b), c2(a), c3 = C(a); 29 // C c4 = a; 30 // D d1(a); 31 D d2(b);
对象a、b和c1创建时没有隐式转换。
对象c2和c3创建时,编译器将参数a隐式转换为一个临时的TypeB对象;d2创建时,编译器将参数b隐式转换为一二临时的TypeC对象。
对象c4不能被创建,因为编译器并不负责两次隐式转换(TypeA–>TypeB–>TypeC);同样d1也不能创建同样是因为需要两次隐式转换(TypeA->TypeB->TypeC)。
另外,copying函数指的是拷贝构造函数和赋值函数。
今天看《C++标准程序库:自修教程与参考手册》中关于auto_ptr的部分,提到它的copying函数只能用auto_ptr作参数,不能用普通指针。请看一下代码所表现的:
1 #include <memory> 2 using namespace std; 3 4 class A 5 { 6 }; 7 8 auto_ptr<A> pa(new A); 9 auto_ptr<A> pb = pa; 10 auto_ptr<A> pc = auto_ptr<A>(new A); 11 // auto_ptr<A> pd = new A;
pb和pc可以被auto_ptr<A>正确赋值,而pd不能被普通指针A*赋值。
这里涉及到的问题有两个:
- 为何要规定这种行为
- 内部是如何去实现的
第一个问题比较明了,因为auto_ptr中有“资源所有权”的问题,每时每刻,某一个资源(比如一块内存神马的)只能属于一个auto_ptr,因此在它的copying函数实现中,必定会包含有资源所有权转移的代码。而普通指针并没有所有权的概念,当然不能被作为copying函数的参数。这种行为肯定是应该禁止的。
而要回答第二个问题,请先看看本文开头描述的两种行为中的第一种,按说,在auto_ptr_copying中的pd被赋值时,new A应该可以由编译器隐式转换为anto_ptr<A>类型的,怎么就失败了呢! 打开auto_ptr定义所在stl中的memory文件,源码是这样的:
1 template<typename _Tp> 2 class auto_ptr 3 { 4 private: 5 _Tp* _M_ptr; 6 7 public: 8 /// The pointed-to type. 9 typedef _Tp element_type; 10 11 /** 12 * @brief An %auto_ptr is usually constructed from a raw pointer. 13 * @param p A pointer (defaults to NULL). 14 * 15 * This object now @e owns the object pointed to by @a p. 16 */ 17 explicit 18 auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { } 19 20 // other source code 21 // ...... 22 }
注意第17行上的修饰构造函数的explicit关键字,它规定auto_ptr的构造函数不能参与隐式转换(在隐式转换时禁止调用),只能显式进行转换,因此在auto_ptr_copying中没有隐式转换。问题貌似到此已经清楚了。
那么,如果不计后果地把这个explicit给去掉呢?是不是就可以进行隐式转换了?结果是否定的。
auto_ptr的copying函数大有玄机,回头看看本文开头描述的两种行为中的第二种,再结合这篇专门讲述auto_ptr_ref的文章,应该就豁然开朗了!