標籤:explicit rtu 額外 style 模型 new 聲明 amp 程式
5.4 對象的效率 (Object Efficiency) 在下面的效率測試中,物件建構和拷貝所須要的成本是以Point3d class聲明為基準,從簡單形式逐漸到複雜形式,包含Plain Ol‘ Data,抽象資料類型(ADT),單一繼承,多重繼承,虛擬繼承,下面函數是測試的主角:
Point3d lots_of_copies(Point3d a, Point3d b) { Point3d pC = a; pC = b; // 1 b = a; // 2 return pC;} 它內帶四個memberwise初始化操作,包含兩個參數,一個傳回值以及一個局部對象pC,它也內帶兩個memberwise拷貝操作,各自是1和2的pC和b.main()函數例如以下:
main() { Point3d pA(1.725, 0.875, 0.478); Point3d pB(0.315, 0.317, 0.838); Point3d pC; for (int iters = 0; iters < 10000000; iters++) pC = lots_of_copies(pA, pB); return 0;} 在最初兩個程式中,資料類型是一個 struct 和一個擁有 public 資料的 class;
struct Point3d { float x, y, z; };
class Point3d { public: float x, y, z; }; 對pA和pB的初始化操作是通過 explicit initialization list來進行的:
Point3d pA = {1.725, 0.875, 0.478};
Point3d pB = {0.315, 0.317, 0.838};
這兩個動作表現出bitwise copy語意,所以應該能夠預期它們的運行有最好的效率.
"memberwise"初始化操作和拷貝操作(Initialization and Copy): Public Data Members和Bitwise Copy Semantics.
CC的效率比較好,是由於NCC迴圈中產生了六個額外的組合語言指令.這個額外負擔並不會反映出不論什麼特定的C++語意,這兩個編譯器的"中間C輸出"大致是相等的.
5.5 解構語意學 (Semantics of Destruction)
假設 class 未定義destructor,那麼僅僅有在 class 內帶的member object(或是 class 自己的base class)
擁有destructor的情況下,編譯器才會自己主動合成出一個.否則,destructor會被視為不須要,也就不需被合成(當然更不須要被調用).比如,Point,預設情況下就沒有被編譯器合成出一個destructor--儘管它擁有一個 virtual function:
class Point {public: Point(float x = 0.0, float y = 0.0); Point(const Point &); virtual float z();private: float _x, _y;}; 類似的道理,假設把兩個Point對象組合成一個Line class:
class Line {public: Line(const Point &, const Point &); virtual draw();protected: Point _begin, _end;}; Line也不會擁有一個合成出來的destructor,由於Point並沒有destructor.
當從Point派生出Point3d(即便是一種虛擬派生關聯)時,假設沒有聲明一個destructor,編譯器就沒有必要合成一個destructor.
不論Point或Point3d,都
不須要destructor,為它們提供一個destructor反而不符合效率.應該拒絕那種"對稱策略"的想法:"已經定義了一個constructor,所以感覺必須提供一個destructor".其實,應該"須要"而非"感覺"來提供destructor,更不要由於不確定是否須要一個destructor,於是就提供它.
為了決定 class 是否須要一個程式層面的destructor(或是constructor),請想想一個 class object的生命在哪裡結束(開始)?
須要什麼操作才幹保證對象的完整?
這是敲代碼時比較須要瞭解的.這也是constructor和destructor什麼時候起作用的關鍵.比如,已知:
{ Point pt; Point *p = new Point3d; foo(&pt, p); delete p;} 能夠看到,pt和p在作為foo()函數的參數之前,都必須先初始化為某些座標值.這時候須要一個constructor,否則使用者必須明白地提供座標值.
一般而言,class 的使用者沒辦法檢驗一個local變數或heap變數以知道它們是否被初始化.把constructor想象為程式的一個額外負擔是錯誤的,
由於它們的工作有其必要性.假設沒有它們,抽象化的使用就會有錯誤的傾向.
當明白地 delete 掉p時會怎樣呢?在不論什麼程式上必須處理的嗎?
是否須要在 delete 之前這麼做:
p->x(0);p->y(0);
不,
當然不須要.沒有不論什麼理由說明在 delete 一個對象之前先得將其內容清除乾淨.也不須要歸還不論什麼資源.在結束pt和p的生命之前,沒有不論什麼"class使用者層面"的程式操作是絕對必要的,因此,也就不一定須要一個destructor.
然而考慮Vertex class,它維護了一個由緊鄰的"頂點"所形成的鏈表,而且當一個頂點的生命結束時,在鏈表上來回移動以完畢刪除操作.假設這正是程式猿所須要的,那這就是Vertex destructor的工作.
當從Point3d和Vertex派生出Vertex3d時,假設不供應一個 explicit Vertex3d destructor,那麼還是希望Vertex destructor被調用,以結束一個Vertex3d object.因此,編譯器必須合成一個Vertex3d destructor,其唯一的任務就是調用Vertex destructor.假設提供一個Vertex3d destructor,編譯器會擴充它,使它調用Vertex destructor.一個由程式猿定義的destructor被擴充的方式類似constructors被擴充的方式,但順序相反:
1.假設object內帶一個vptr,那麼首先重設(reset)相關的 virtual table.
2.destructor的函數本身如今被運行,也就是說vptr會在程式猿的碼運行前被重設(reset).
3.假設 class 擁有member class objects,而後者擁有destructors,那麼它們會以聲明順序的相反順序被調用.
4.假設有不論什麼直接的(上一層)nonvirtual base classes擁有destructor,它們會以其聲明順序的相反順序被調用.
5.假設有不論什麼的 virtual base classes擁有destructor,而當前討論的這個 class 是最尾端(most-derived)的 class,那麼它們會以其原來的構造順序的相反順序被調用.
(這個順序似乎有問題,應該為2,3,1,4,5.當中1在2,3之後)
就像constructor一樣,
眼下對於destructor的一種最佳實現策略就是維護兩份destructor實體:
1.一個complete object實體,總是設定好vptr,並調用 virtual base class destructors.
2.一個base class subobject實體:除非在destructor函數中調用一個 virtual function,否則它絕不會調用 virtual base class destructors並設定vptr.
一個
object的生命結束於其destructor開始運行之時,
因為每個base class destructor都輪番被調用,所以derived class 實際上變成了一個完整的object.比如一個PVertex對象歸還其記憶體空間之前,會依次變成一個Vertex3d對象,一個Vertex對象,一個Point3d對象,最後成為一個Point對象.當在destructor中調用member functions時,對象的蛻變會由於vptr的又一次設定(在每個destructor中,在程式猿所提供的代碼之前)而受到影響.在程式中實施destructor的真正語言將在第6章詳述.
C++物件模型——解構語意學(第五章)