C++對象模型(三):Program Transformation Semantics (程序轉換語義學)
本文是Inside The C++ Object Model Chapter 2 部分的讀書筆記。是討論編譯器調用拷貝構造函數時的策略(如何優化以提高效率),侯捷稱之為"程序轉化的語義學"
或者說是是關於編譯器對於程序是如何進行有效轉化或者說翻譯,以實現C++的語法機製。主要來說有以下幾種Semantics:
1) 明確的初始化操作(Explicit Initialization)
比如定義: X x0;
那麼以下定義: X x1(x0); X x2 = x0; X x3 = X(x0); 都會被轉化成: X x1,x2,x3; 在這裏編譯器並不會做這三個object的初始化,而是調用copy constructor進行初始化:
x1.X::X(x0); x2.X::X(x0); x3.X::X(x0);
2) 參數初始化(Argument Initialization)
C++ Standard ( Section 8.5)說,把一個class object 當做參數傳遞給一個函數或者把它作為一個函數的返回值時,相當於以下形式的初始化操作:
X xx = arg;其中xx是形式參數或者返回值,arg代表真正的參數值,因此類似於void foo(X x0);這種調用,將會使得local instance x0以memberwise的形式以實際參數為初始值進行初始化。
一般來說,編譯器有兩種做法:
a) introduce a temporary object
還是以上文的函數聲明 void foo(X x0);
調用進入後,1、編譯器生成一個temporary object:X _temp;
2、以實際參數xx 拷貝構造 這個temporary object:_temp.X::X(xx);
3、重新改寫函數調用操作,foo(_temp);
4、最重要的一點就是修改參數調用方式為引用,否則如何工作又回到原點啦。。。void foo(X &x0);
b) 將參數直接以copy constructor建構到函數的堆棧上,這樣也會有一個local object生成;當然在函數返回時該local object也會被destructed。3) 返回值的初始化(Return Value Initialization)
當返回值是object時,這個object是如何返回的呢?cfront使用的是一個雙階段轉化:
a) 首先加上一個額外的參數,是class object的reference,這個參數將放置通過copy constructor得來的返回值
b)在return之前安插一個copy constructor,以便將欲傳回之的object當做上述新增參數的處置。
例如:X bar() { X xx; return xx;} 會被轉化為:
void bar(X & _res) //這裏安插了臨時引用參數
{
X xx;
xx.X::X();
_res.X::X(xx); //這裏安插了臨時引用的拷貝構造函數
return;
}
現在編譯器必須轉化每個bar調用,以反映其新定義。例如X xx = bar(); ===> X xx; bar( xx );
而相應的 bar().memfunc(); //執行bar()所返回之X class object的member function
會被編譯器轉化為:
X temp0;
(bar(temp0),temp0).memfunc();
在返回值優化上,Optimization at the User Level or Optimization at the Compiler Level。在User Level, 設計者需要創建不同的constructor,這樣object直接通過計算,而不需要copy constructor。這樣做如果在非常注重效率的場合可能比較有意義,但是缺乏抽象。
在Compiler Level,現在廣為認知的就是 Name Return Value(NRV)Optimization:
void bar(X & _res) //這裏安插了臨時引用參數
{
_res.X::X();
//直接操作_res
return;
}
也就是說,NRVO 省略了一次copy constructor的調用。但是如果copy constructor有side-effect的話,那麼這個優化就會有問題。
此語錯誤。黃先生認為如果程式沒有 explicit copy constructor,編譯器會自動為我們做出來(如為 trivial,則直接 bitwise copy;如為 nontrivial,則由編譯器為我們合成出一個 copy constructor)。因此,有沒有 explicit copy constructor 並不影響 NRV 最佳化的實施。他認為 NRV 最佳化主要是 由編譯器 option 來決定要不要實施。他並且做了一些實驗,判斷 VC 和 gcc 都沒有做到 NRV 最佳化,而其不做的理由不是因為技術上的困難,是為了避免造成「user defined copy constructor 之副作用失效」-- 所謂副作用
是指,例如「在 user defined copy constructor 中做一個 cout 輸出」之類這種「與 memberwise copy 無關」的動作。
NRV優化還是很重要,比如下麵的代碼,如果沒有NRV將會有三次copy 構造,二次析構:
Type get(int I) { return Type(i); } Type t = get(1);
甚至有人認為user defined copy constructor會阻止NRV的優化。更多討論可以參見:關於編譯器對拷貝構造函數優化的問題再討論
最後更新:2017-04-03 12:53:42