閱讀993 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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的話,那麼這個優化就會有問題。

        書中提到,如果某個class 會有大量的object return value的情況,那麼需要為該class define copy constructor,以觸發NRV(或者叫RVO, Return Value Optimization)。但是,黃俊達先生認為:Lippman 在 p67 最後一行所言『這個程式的第一個版本不能實施 NRV 最佳化,因為 test class 缺少一個 copy constructor』,
此語錯誤。黃先生認為如果程式沒有 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

  上一篇:go C++編程規範之9:不要進行不成熟的劣化
  下一篇:go java備忘