1. 程式人生 > >C++對象模型——暫時性對象 (第六章)

C++對象模型——暫時性對象 (第六章)

|| int 求值 運算 const 三種方式 設有 必須 u+

6.3 暫時性對象 (Temporary Objects)

假設有一個函數,形式例如以下:
T operator+(const T &, const T &);
以及兩個T objects,a和b,那麽:
a + b;
可能會導致一個暫時性對象,以放置傳回的對象.是否會導致一個暫時性對象,視編譯器的進取性(aggressiveness)以及上述操作發生時的程序上下關系(program context)而定.比如以下這個片段:
T a, b;
T c = a + b;
編譯器會產生一個暫時性對象,放置a+b的結果,然後再使用T的copy constructor,把該暫時性對象當作c的初值.然而更好的轉換則是直接以拷貝構造的方式,將a+b的值放到c中,於是就不須要暫時性對象,以及對其constructor和destructor的調用了
.

此外,視operator+()的定義而定,named return value(NRV)優化(詳見2.3節)也可能實施起來.這將導致直接在上述c對象中求表達式結果,避免運行copy constructor和具名對象(named object)的destructor.
三種方式所獲得的c對象,結果都一樣.其間的差異在於初始化的成本.一個編譯器可能給不論什麽保證嗎?嚴格來說沒有,C++ Standard同意編譯器對於暫時性對象的產生有全然的自由度.
但實際上,差點兒不論什麽表達式假設有這樣的形式:
T c = a + b;
當中的加法運算符被定義為:
T operator+(const T &, const T &);

T T::operator+(const T &);
那麽實現時根本不產生一個暫時性對象.
然而註意,意義相當的assignment語句:
c = a + b;
不可以忽略暫時性對象.
所以這種初始化操作:
T c = a + b;
總是比以下的操作更有效率地被編譯器轉換:
c = a + b;
第三種運算形式是,沒有出現目標對象:
a + b;    // no target
這時候有必要產生一個暫時對象,以放置運算後的結果.盡管看起來有點怪異,但這樣的情況實際上在子表達式中十分普遍.比如,假設這樣寫:
String s("hello"), t("world"), u("!");
那麽不論:
String v;
v = s + t + u;

printf("%s\n", s + t);
都會產生一個暫時對象,與s + t相關聯.
最後一個表達式帶來了一個論題,那就是"暫時對象的生命周期".
一種比較被喜歡的轉換方式是在調用printf()之後實施String destructor.在C++ Standard下,這正是該表達式的必須轉換方式.標準規格這樣將:
暫時性對象的被摧毀,應該是對完整表達式求值過程中的最後一個步驟,該表達式造成暫時對象的產生.
什麽是一個完整表達式?非正式地說,它是被涵括的表達式中最外圍的那個.以下這個表達式:
// tertiary full expression with 5 sub-expressions
((objA > 1024) && (objB > 1024)) ?

objA + objB : foo(objA, objB);

一種有五個子表達式,內帶在一個"?:完畢表達式"中.不論什麽一個子表達式所產生的不論什麽一個暫時對象,都應該在完整表達式被求值完畢後,才幹夠銷毀.
當暫時性對象是依據程序的運行期語意有條件地被產生出來時,暫時性對象的生命規則就顯得有些復雜了.舉個樣例,想這種表達式:
if (s + t || u + v)
當中的u+v子算式僅僅有在s+t被評估為 false 時,才會開始被評估.與第二個子算式有關的暫時性對象必須被銷毀.可是,非常顯然地,不能夠被無條件地銷毀.也就是說,希望僅僅有在暫時性對象被產生出來的情況下才去銷毀它.(假設第一個子算式為 true,則不產生第二個暫時性對象,不須要銷毀)
把暫時性對象的destructor放在每個子算式的求值過程中,能夠免除"努力追蹤第二個子算式是否真的須要被評估".然而在C++ Standard的暫時對象生命規則中,這種策略不再被同意.暫時性對象在完整表達式尚未評估全然之前,不得被銷毀.也就是說,某些形式的條件測試如今必須被插入進來,以決定是否要曉輝何第二算式有關的暫時對象.
暫時對象的生命規則有兩個例外.第一個例外發生在表達式被用來初始化一個object時.比如:
bool verbose;
...
String progNameVersion = !verbose ? 0 : progName + progVersion;
當中progName和progVersion都是String objects.這時候會生出一個暫時對象,放置加法運算符的運算結果:
String operator+(const String &, const String &);
暫時對象必須依據對verbose的測試結果有條件地解構.在暫時對象的生命規則下,它應該在完整的"?

:表達式"結束評估後盡快被銷毀.然而,假設progNameVersion的初始化須要一個copy constructor:

progNameVersion.String::String(temp);
那麽暫時性對象的解構(在"?

:完整表達式"之後)當然那就不是期望的.C++ Standard要求:
...凡是含有表達式運行結果的暫時性對象,應該存留到object的初始化操作完畢為止.
暫時性對象的生命規則的第二個例外是"當一個暫時性對象被一個reference綁定"時,比如:

const String &space = " ";
產生出這種程序代碼:
// C++ pseudo Code
String temp;
temp.String::String(" ");
const String &space = temp;
非常明顯,假設暫時性對象如今被銷毀,那個reference也就沒實用了.所以C++ Standard要求:
假設一個暫時性對象被綁定在一個reference,對象將殘留,直到被初始化的reference的生命結束,或直到暫時對象的生命範疇(scope)結束--視哪一種情況先到達而定
.

暫時性對象的迷思

有一種說法是,因為當前的C++編譯器會產生暫時性對象,導致程序的運行比較沒有效率.

C++對象模型——暫時性對象 (第六章)