深度探索c++對象模型之類對象的賦值

      在C++中,當我們聲明一個類時,如果沒有給這個類操作符“=”定義一個函數,那麼一般情況下編譯器會自動爲這個類合成一個默認的copy assignment operator【拷貝賦值操作符】,而這種copy assignment operator的工作模式是bitwise copy,所謂的bitwise copy,意思就是按位施以拷貝【兩個類對象除了在內存中的位置不一樣,其它的一模一樣】。但這種模式是存在一定危險性的,比如,考慮一個虛繼承A和B,B繼承自A,我們先聲明瞭一個B對象b,然後聲明一個A對象a,接着再聲明一個A指針對象p;首先,我們讓p指向&b,然後再把*p賦值給a,很顯然這就是一種錯誤【在bitwise copy情況下】,因爲*p中的所有內容都原封不動照搬給A對象a了,那麼也包括*p中的虛表指針,但實際上這個虛表指針是指向B類的,而a中的虛表指針正確情況下應該指向A類!所以編譯器是會選擇性的給類默認合成bitwise copy式copy assignment operator的,在以下四種情況下,編譯器是不會合成出一個bitwise copy模式的copy assignment operator的:

1):一個類中帶有成員類對象,而這個內嵌類含有自己的【用戶定義】copy assignment operator。

2):一個類的基類有copy assignment operator。

3):當一個類中聲明瞭任何virtual function【虛函數】時。

4):當一個類繼承自virtual base class時【無論這個基類有沒有copy assignment operator】。

      所以,如果我們定義這樣一個Point類:

class Point{
public:
Point(int x=0, int y=0):_x(x),_y(y){};
protected:
int _x,_y;
}

然後我們再寫【Point a,b;......b=a;......】時,其中的【b=a;】就是按位照搬【bitwise copy】,這期間並沒有調用什麼copy assignment operator,而且這種模式效率還很高。

      現在,讓我們來給Point導入一個copy assignment operator:

inline Point&
Point::operator=(const Pont &p)
{
_x=p.x;
_y=p.y;
return this;
}
接着,讓我們再定義一個Point3d類,該類虛擬繼承自Point:

class Point3d:virtual public Point
{
public:
Point3d(int _x=0,int _y=0,int _z=0);
protected:
int _z;
} 

在這裏,如果我們沒有爲Point3d定義一個拷貝賦值操作符,編譯器就會爲Point3d合成一個類似這樣的拷貝賦值操作符【僞代碼】:

inline Point3d&
Point3d::operator=(Point3d* const this, const Point3d &p)
{
//調用基類的函數實體
this->Point::operator=(p);

//memberwise copy the derived class members
_z = p->_z;

return *this;
}

      與constructor不同的是,constructor可以有一個member initialization list,而copy assignment operator卻不能有一個member assignment list,因此以下代碼是非法的:

//非法代碼
inline Point3d&
Point3d::operator=(const Point3d &p3d):Point(p3d),_z(p3d._z)
{}

要改成如下形式,才能通過編譯:1)【Point::operator=(p3d);】或者【( *(Point *) this ) = p3d;】

      好了,現在讓我們再創建兩個類,Vertex和Vertex3d。其中,Vertex跟Point3d一樣,也是虛擬繼承自Point;而Vertex3d,則派生自Point3d和Vertex,爲了簡潔,本文不再贅述這兩個類的代碼,而是直接看它們的copy assignment operator函數定義:

//Vertex的拷貝賦值操作符
inline Vertex&
Vertex::operator=(const Vertex& v)
{
this->Point::operator(v);

_next = v._next;//這裏的_next是Vertex新增的一個指針成員

return *this;
}

//Vertex3d的拷貝賦值操作符
inline Vertex3d&
Vertex3d::operator=(const Vertex3d &v3d)
{
this->Point::operator=(v3d);
this->Point3d::operator=(v3d);
this->Vertex::operator=(v3d);
...//其它一些Vertex3d自己成員的拷貝賦值【如果有的話】
return *this;
}

仔細看上面那兩段代碼,您會發現問題嗎?嗯,那就是在給Vertex3d類的對象們拷貝賦值時,Point的copy assignment operator會被調用三次!那麼有什麼辦法可以壓抑限制一下基類的copy assignment operator嗎,比如像constructor中傳遞一個額外的參數?書中給出的答案是不行!但是爲什麼不行,我當時是左看右看都不明白!我想了想,還是先上傳書中的原話吧:


因爲自己看不懂的緣故,無奈之下,我去知乎求助,有幸遇到一位好心大牛的回覆,總算明白了一點。關鍵在於那句紅橫線的話“取copy assignment operator函數地址是合法的”,在徹底理解這句話的背後含義之前,讓我們先來回顧一下constructor的額外參數選構法:就是在隱式參數this後面,通過多傳遞一個叫做“_most_derived”的額外參數,利用它來判定要不要調用基類構造函數。同樣的類似方法,爲什麼不能適用於copy assignment operator呢?讓我們看一下上圖中作者給出的代碼,其中的第二句代碼,可以被轉換成如下形式:

Point3d& (Point3d::*pmf)(const Point3d&) = Point3d::operator=;
如果編譯器仿照constructor,給copy assignment operator裏面加一個額外的參數,那麼在給函數指針pmf聲明時參數列表裏面就應該加上一個bool型【因爲額外參數的類型很可能就是bool型】,所以上圖書中的那些代碼根本就編譯不通過。總之就是如果有額外參數的話,我們在聲明一個copy assignment函數指針的時候,參數表裏面不應該只有一個“const Point3d&”。C++先驅們很顯然不希望用戶們爲這個參數表而頭疼,所以就索性沒有仿照constructor的做法。。。

      那麼是不是可以通過產生一些分化函數【split function】來解決這個問題呢?比如在編譯時將copy assignment operator分化爲兩個,讓它們的參數列表不同【有無額外參數】,一個用來對付函數指針問題,一個用來對付高效率拷貝問題。知乎大牛給出的回答是:符合語意!但是編譯出來的代碼體積將大大增加,也許會得不償失。

      事實上,很多編譯器根本就不打算在這上面嘗試過多的努力,所以像我們上面的例子,那個Point類的copy assignment operator會被調用多次。大家並不在乎這些效率上的浪費,因爲C++標準都說了:我們並沒有規定那些代表virtual base class的subobject是否該被“隱喻定義(implicitly define)的copy assignment operator”指派內容一次以上。(C++ Standard,Section 12.8)

      本文的最後,首先套用作者的話給大家一個建議:不要在任何virtual base class中聲明數據成員!其次,再套用知乎大牛的題外話作爲結束:

對象構造和對象複製,有很多類似的地方,但也需要注意它們的區別。《深度探索 C++ 對象模型》很多章節,都是先講述對象構造怎麼怎麼樣,再講述對象複製時怎麼樣。平時寫代碼也是,對象構造、複製、釋放,都需要特別注意。另外這本書有些術語似乎跟大陸的其術語不太一致,看的時候注意。比如書中常說的對象複製、對象拷貝,經常是指賦值。而我們大陸這邊,說對象複製,通常聯想到複製構造函數。英文就沒有歧義,一個是 copy assignment operator, 一個是 copy constructor。C++ 不斷爲舊特性打補丁,virtual 繼承這特性已入歧途。Point 的數據會重複是因爲使用了多重繼承,假如一開始就只有單繼承,並添加接口,就沒有之後的一系列問題。這些看似複雜的問題,是自己弄出來的。《深度探索 C++ 對象模型》這本書中,一旦涉及到 virtual 繼承,編譯器實現時就會有很多小花招,那些章節看起來也比較難懂。實際工程中不應該使用多繼承,而只使用“單繼承 + 接口”,接口概念在 C++ 中實現爲沒有數據的純虛類。這樣關於 virtual 一系列的章節,實際工程中沒有那麼重要。


發佈了53 篇原創文章 · 獲贊 113 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章