標籤:地方 dia ++ mpi asp res 繼承 cti 物件模型
5.3 對象複製語意學 (Object Copy Semantics) 當設計一個 class,並以一個 class object指定給 class object時,有三種選擇:
1.什麼都不做,因此得以實施預設行為.
2.提供一個 explicit copy assignment operator.
3.明白地拒絕一個 class object指定給還有一個 class object.
假設要
選擇第3點,不同意將一個 class object指定給還有一個 class object,那麼
僅僅要將copy assignment operator聲明為 private,而且不提供其定義就可以.
把它設定為 private,就不再同意於不論什麼地點(除了在member functions以及此 class 的friend中)
進行賦值(assign)操作.不提供其函數定義,則一旦某個member function或 friend 企圖影響一份拷貝,程式在連結時就會失敗.一般覺得這和連結器的性質有關.
在這一節,驗證copy assignment operator的語意,以及它們怎樣被模塑出來,利用Point class 來協助討論:
class Point {public: Point(float x= 0.0, float y = 0.0); // ... 沒有virtual functionprotected: float _x, _y;}; 沒有理由須要禁止拷貝一個Point object.因此問題就變成了:預設行為是否足夠?
假設要支援的僅僅是一個簡單的拷貝操作,那麼預設行為不但足夠並且有效率,沒有利用再自己提供一個copy assignment operator.
僅僅有在預設行為導致的語意不安全或者不對時,才須要設計一個copy assignment operator.預設的memberwise copy行為對Point不安全嗎?不對嗎?
不,因為座標都內帶數值,所以不會發生"別名話"或"記憶體泄露".假設自己提供copy assignment operator,程式反倒會啟動並執行比較慢.
假設不正確Point供應一個copy assignment operator,而僅僅是依賴預設的memberwise copy,編譯器會產生出一個實體嗎?這個答案和copy constructor的情況一樣:實際上不會!因為此 class 已經有了bitwise copy語意,所以implicit copy assignment operator被視為毫無用處,也根本不會被合成出來.
一個 class 對於預設的copy assignment operator,在下面情況不會表現出bitwise copy語意:
1.當 class 內帶一個member object,而其 class 有一個copy assignment operator時.
2.當一個 class 的base class 有一個copy assignment operator時.
3.當一個 class 聲明了不論什麼 virtual functions(一定不可以拷貝右端 class object的vptr地址,由於它可能是一個derived class object).
4.當 class 繼承自一個 virtual base class(不論此base class 有沒有copy operator)時.
C++ Standard上說copy assignment operators並不表示bitwise copy semantics是nontrivial.實際上,僅僅有nontrivial instances才會被合成出來.
於是,對於Point class,這種賦值(assign)操作:
Point a, b;a = b;
由bitwise copy完畢,把Point b拷貝給Point a,其間並沒有copy assignment operator被調用.從語意上或從效率上考慮,這都是所須要的.注意,還是可能提供一個copy constructor,為的是把name return value(NRV)最佳化開啟.copy constructor的出現不應該暗示出也一定要提供一個copy assignment operator.
如今匯入一個copy assignment operator,用以說明該operator在繼承之下的行為:
inline Point &Point::operator=(const Point &p) { _x = p._x; _y = p._y;} 如今派生出一個Point3d class(請注意是虛擬繼承):
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++虛擬碼:被合成的copy assignment operatorinline 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有一個非正交性情況(nonorthogonal aspect,意指不夠理想,不夠嚴謹的情況),那就是它缺乏一個member assignment list.因此不能重寫:
// C++虛擬碼,下面性質並不支援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被調用.比如,以下是個Vertex copy operator,當中Vertex也是虛擬繼承自Point:
// class Vertex : virtual public Pointinline Vertex &Vertex::operator=(const Vertex &v) { this->Point::operator(v); _next = v._next; return *this;} 如今從Point3d和Vertex中派生出Vertex3d,以下是Vertex3d的copy assignment operator:
inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) { this->Point::operator=(v); this->Point3d::operator(v); this->Vertex::operator=(v);} 編譯器怎樣可以在Point3d和Vertex的copy assignment operators中壓抑Point的copy assignment operators呢?編譯器不可以反覆傳統的constructor解決方式(附加上額外的參數).這是由於,和constructor以及destructor不同的是,"取copy assignment operator地址"的操作是合法的.因此,以下這個範例是合法程式碼(儘管它也推翻了希望把copy assignment operator做的更靜止的企圖):
typedef Point3d &(Point3d::*pmfPoint3d) (const Point3d &);pmfPoint3d pmf = &Point3d::operator=;(x.*pmf)(x);
//看不懂.................................
然而無法支援它,仍然須要依據其獨特的繼承體系,插入不論什麼可能數目的參數給copy assignment operator.這一點在支援由 class object(內帶 virtual base class)所組成的數組的配置操作時,也被證明是非常有問題的(詳見6.2節)
還有一個方法是,編譯器可能為copy assignment operator產生分化函數(split functions),以支援這個 class 成為most-derived class 或成為中間的base class.假設copy assignment operator被編譯器產生的話,那麼"slit function解決方式"可說是定義明白.但假設它是被 class 設計者所完畢的,那就不算是定義明白.比如,怎樣分化像以下這種函數(特別是當init_bases()為 virtual 時):
inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) { init_bases(v);}
其實,copy assignment operator在虛擬繼承情況下行為不佳,須要特別小心地設計和說明.
假設使用一個以語言為基礎的解決方案,那麼應該為copy assignment operator提供一個附加的"member copy list".簡單地說,不論什麼解決方式假設是以程式操作為基礎,將導致較高的複雜度和較大的錯誤傾向.一般公認,這是語言的一個弱點,也是應該小心檢驗程式碼的地方(當使用 virtual base classes時).
有一種方法能夠保證most-derived class 會引發 virtual base class subobject的copy行為,那就是在derived class 的copy assignment operator函數實體的最後,明白地調用那個operator,像這樣:
inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) { this->Point3d::operator=(v); this->Vertex:;operator=(v); // must place this last if your compiler dose not suppress intermediate class invocations this->Point::operator=(v);} 這並不能省略subobjects的多重拷貝,但卻能夠保證語意正確.還有一個解決方式要求把 virtual subobjects複製到一個分離的函數中,並依據call path條件化調用它.
最好的辦法是
儘可能不要同意一個 virtual base class 的拷貝操作.甚至有一個奇怪的方法是:
不要在不論什麼 virtual base class 中聲明資料
C++物件模型——對象複製語意學 (Object Copy Semantics)(第五章)