1. 程式人生 > >5.3 物件複製語意學(Object Copy Semantics)

5.3 物件複製語意學(Object Copy Semantics)

當我們設計一個class,並以一個class object賦值給另一個class object時,我們就用到了拷貝複製操作,我們可以選擇預設的淺拷貝,也可以自己提供一個explicit copy assignment operator,或者顯示的拒絕把一個class object賦值給另一個class object(將copy assignment operator宣告為private)。

為了驗證copy assignment operator,再一次用到Point class:

class Point
{
public:
	Point(float x = 0.0,float y = 0.0,float z = 0.0);
	//...(沒有virtual function)
protected:
	float _x,_y;
};

對於上述程式碼,如果要支援一個簡單的拷貝操作,那麼預設的行為不但足夠而且有效率,只有在預設行為所導致語義不安全或不正確的情況下,才需要設計一個copy assignment operator。

由於此class已經有bitwise copy語意,所以implicit copy assignment operator和copy constructor一樣被視為毫無用處,也根本不會合成出來。

C++ Standard上說copy assignment operators並不表示bitwise copy semantics是nontrivial,只有nontrivial 才會被合成出來,以下情況不會表現bitwise copy語意:

  1. 當class內含一個member object,而其class有一個copy assignment operator時。
  2. 當一個class 的base class有一個copy assignment operator 時。
  3. 當一個class聲明瞭任何virtual function(我們一定不要拷貝右端class object的vptr地址,因為它可能是一個derived class object)。
  4. 當class 繼承自一個virtual base class(不論此時base class有沒有copy operator)時。

對於Point class,這樣的賦值操作:

Point a,b;
...
a = b;

由於是bitwise copy完成,把Point b拷貝到Point a,期間沒有copy assignment operator被呼叫;我們還可以提供一個copy constructor,把NRV優化開啟,copy constructor出現不應該讓我們也一定提供一個copy assignment operator。

現在匯入一個copy assignment operator,來說明該operator在繼承下的行為:

inline Point&
Point::operator = (const Point& rhs)
{
	_x = rhs._x;
	_y = rhs._y;
	return *this;
}

現在派生一個Point3d class(虛擬繼承):

Point3d : virtual public Point
{
public:
	Point3d(float x = 0.0,float y = 0.0,float z = 0.0);
	//...
protected:
	float _z;
};

如果我們沒有為Point3d定義一個copy assignment operator,編譯器就必須合成一個(因為前述第二項和第四項),合成的看起來像如下:

//C++ pseudo
//被合成的copy assignment operator
inline Point3d&
Point3d::operator = (Point3d* const this,const Point3d& p)
{
	//呼叫base class的拷貝賦值函式
	this->Point::operator(p);
	
	//memberwise copy the derived class members 
	_z = p.z;
	return *this;
}

copy assignment operator有一個不夠理想、不夠嚴謹的情況,就是它缺乏一個member assignment list(不能像建構函式那樣member initialization list),因為不能寫:

//C++ pseudo,以下性質不支援
inline Point3d&
Point3d::operator = (const Point3d& p3d)
	:Point(p3d),z(p3d._z)
{}

//必須寫成以下的兩種形式,
//才能呼叫base class 的copy assignment operator
Point::operator = (p3d);
//或者
(*(Point)this) = p3d;

缺少copy assignment list,編譯器就無法壓抑上一層base class的copy operators被呼叫:

//class Vertex: virtual public Point
inline Vertex&
Vertex::operator = (const Vertex& v)
{
	this->Point::operator(v);
	_next = v._next;
	return *this;
}

//從Point3d和Vertex中派生出Vertex3d
inline Vertex3d&
Vertex3d::operator = (const Vertex3d& v)
{
	this->Point::operator(v);
	this->Point3d::operator(v);
	this->Vertex::operator(v);
	//...
}

事實上,copy assignment operator在虛擬繼承情況下行為不佳,需要小心的設計和說明。許多編譯器甚至並不嘗試取得正確語意,它們在每一箇中間的copy assignment operator中呼叫每一個base class instance,於是造成virtual base class copy assignment operator的多個例項被呼叫。

有一種方法可保證most_derived class會引發virtual base class subobject的copy行為,就是在derived class的copy assignment oper函式例項的最後,顯示呼叫那個operator:

inline Vertex3d&
Vertex3d::operator = (const Vertex3d& v)
{
	this->Point3d::operator(v);
	this->Vertex::operator(v);
	//must place this last if your
	//compiler does not suppress
	//intermediate class invocations
	this->Point::operator(v);
	/...
}

這並不能省略subobjects的多重拷貝,但卻可以保證語意正確。另一個方法就是把virtual subobject拷貝到一個分離的函式中,並根據call path條件化的呼叫它。 

作者的建議是不要允許virtual baes class的拷貝操作,甚至不要再任何virtual base class中宣告資料。