More Effective C++ 19:理解臨時對象的來源

C++中真正的臨時對象是看不見的,它們不出現在你的源代碼中。
建立一個沒有命名的非堆對象會產生臨時對象。這種未命名的對象通常在兩種條件下產生:
爲了使函數成功調用而進行隱式類型轉換和函數返回對象時.

首先考慮爲使函數成功調用而建立臨時對象這種情況:
當傳送給函數的對象類型與參數類型不匹配時會產生這種情況。
比如:
一個函數,它用來計算一個字符在字符串中出現的次數

size_t countChar(const string& str, char ch);
char buffer[MAX_STRING_LEN];
char c;
cin >> c >> setw(MAX_STRING_LEN) >> buffer;
cout << "There are " << countChar(buffer, c)<< " occurrences of the character " << c << " in " << buffer << endl;

看一下 countChar 的調用。第一個被傳送的參數是字符數組,但是對應函數的正被綁定的參數的類型是 const string&。僅當消除類型不匹配後,才能成功進行這個調用,你的編譯器很樂意替你消除它,方法是建立一個 string 類型的臨時對象。通過以 buffer 做爲參數調用 string 的構造函數來初始化這個臨時對象。countChar 的參數 str 被綁定在這個臨時的 string 對象上。當 countChar 返回時,臨時對象自動釋放。

這種類型轉換很方便,但是從效率的觀點來看,臨時string對象的構造和釋放是不必要的開銷。

通常有兩個方法可以消除它:
1.重新設計你的代碼,不讓發生這種類型轉換。
2.通過修改軟件而不再需要類型轉換。

僅當通過傳值式傳遞對象或傳遞常量引用參數時,纔會發生這些類型轉換。當傳遞一個非常量引用參數對象,就不會發生。考慮下面這個函數:

void uppercasify(string& str);
char subtleBookPlug[] = "Effective C++";
uppercasify(subtleBookPlug); // 錯誤!

在這裏插入圖片描述
沒有爲使調用成功而建立臨時對象,爲什麼呢?
假設建立一個臨時對象,那麼臨時對象將被傳遞到 upeercasify 中,其會修改這個臨時對象,把它的字符改成大寫。但是對 subtleBookPlug 函數調用的真正參數沒有任何影響;僅僅改變了臨時從 subtleBookPlug 生成的 string 對象。無疑這不是程序員所希望的。

對非常量引用進行的隱式類型轉換卻修改臨時對象。這就是爲什麼 C++語言禁止爲非常量引用產生臨時對象。這樣非常量引用參數就不會遇到這種問題。

建立臨時對象的第二種環境是函數返回對象時。比如 operator+必須返回一個對象,以表示它的兩個操作數的和。

const Number operator+(const Number& lhs, const Number& rhs);

這個函數的返回值是臨時的,因爲它沒有被命名;它只是函數的返回值。你必須爲每次調用 operator+構造和釋放這個對象而付出代價。

通常你不想付出這樣的開銷。對於這種函數,你可以切換到 operator=,而避免開銷。

不過對於大多數返回對象的函數來說,無法切換到不同的函數,從而沒有辦法避免構造和釋放返回值。至少在概念上沒有辦法避免它。然而概念和現實之間又一個黑暗地帶,叫做優化,有時你能以某種方法編寫返回對象的函數,以允
許你的編譯器優化臨時對象。這些優化中,最常見和最有效的是返回值優化,這是條款 20的內容。

總結

臨時對象是有開銷的,所以你應該儘可能地去除它們,更重要的是訓練自己尋找可能建立臨時對象的地方。在任何時候只要見到常量引用參數,就存在建立臨時對象而綁定在參數上的可能性。在任何時候只要見到函數返回對象,就會有一個臨時對象被建立(以後被釋放)。學會尋找這些對象構造,你就能顯著地增強透過編譯器表面動作而看到其背後開銷的能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章