C++ 中让对象的拷贝成为 显式 的
C++中对象的拷贝一般使用拷贝构造函数,从而对象的拷贝大多是隐式的,使用拷贝构造函数的隐式拷贝很方便,但是编译器无法识别不必要的拷贝,虽然我们人类可以识别这些不必要的拷贝,比如在写函数原型时,忘了加&,就会引发一个这样的非必要拷贝。
如果这种情况很严重,我们可以禁用拷贝构造函数和赋值函数(声明成private),然后再提供一个显式拷贝函数,如:
1 2 3 4 5 6 7 |
class HeavyObject { HeavyObject(const HeavyObject&); HeavyObject& operator=(const HeavyObject&); public: void clone_to(HeavyObject& dest) const; // more... }; |
这种方法的确可以work,但看上去很不自然,比如:
1 2 3 4 5 |
HeavyObject x, y; // ... y.clone_to(x); // copy y to x // 其实我们更习惯这样的表达: x = y.clone(); |
x = y.clone(); 在形式上很自然,但是实现却不容易(在C++11中实现要容易点,让 x 接受 && 即可)。
但的确可以实现:在 C++98 标准中,如果一个函数返回一个非 Primitive 的值对象,那么这个对象不能被 bind 到非 const 引用上, 但是可以被修改!
我们可以利用标准的这个特点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
template<class T> struct Ref2 : T {}; template<class T> struct Ref1 { // 作为返回值对象,Ref1 可以被修改,但不能绑定到 Ref1& 上 T val; Ref1(const T& y) : val(y) {} operator Ref2<T>&() // this is a non-const member function { return static_cast<Ref2<T>&>(val); } }; class HeavyObject { friend struct Ref1<HeavyObject>; // for Ref1 accessing the private copy-cons HeavyObject(const HeavyObject&); HeavyObject& operator=(const HeavyObject&); public: HeavyObject() {} void swap(HeavyObject& y) { /*non-throw*/ } void operator=(Ref2<HeavyObject>& y) { // prohibit chained assign this->swap(y); // y is the object created by clone } Ref1<HeavyObject> clone() const { // copy-cons is private and has implementation // assume return value optimization is enabled return Ref1<HeavyObject>(*this); } // more... }; |
当执行 x = y.clone() 时,clone 调用Ref1(const HeavyObject& y) 创建一个临时对象并按值返回,该对象不能 bind 到 non-const reference,但可以被修改!
接下来该对象被传给 HeavyObject::operator=,但该 operator= 只接受 Ref2& 或 const HeavyObject&,于是,编译器需要调用一个 user defined type conversion, 在这里,就是Ref1::operator Ref2&,该函数是 non-const,可以被调用(记得前面说的:不能 bind 到 non-const reference,但可以被修改,从而就可以调用
non-const member function)……
于是,接下来的事情就很简单了……