c++臨時變數的作用域

weixin_33890499發表於2016-01-20

上上週週末翻《effective c++》,條款18看到一段程式碼:

class Date {
    public:
    Date(const Month& m, const Day& d, const Year& y);
    ...
};

class Month {
    public:
    static Month Jan() { return Month(1); } 
};
Date d(Month::Jan(), Day(30), Year(1995));

看到這裡的時候,猛然感覺有問題。
在函式d(Month::Jan(), Day(30), Year(1995));中(也就是Data的建構函式),第一個變數為const Month& m,是一個引用,而函式Jan()返回了一個臨時變數(返回值儲存在棧中,之後通過拷貝構造臨時變數)。也就是說,將一個引用指向了一個臨時變數,而且這個臨時變數的作用域非常不明確。初看上去,在Data()的第一個引數賦值完成後,臨時變數就失效了。

當時在網上找了好久,期望看到其他人對這段程式碼的異議,可惜沒有發現。
週二的時候,問了一下師兄。
師兄說這樣是有問題的,估計編譯都會出現警告,但是如果Date的建構函式中做了特殊的實現(比如變數拷貝等等),那麼這樣寫就是正確的。我想了想,確實有道理,師兄還提出了一個觀點:c++裡似乎會延長這樣的臨時變數的作用域。他建議我去看一下這段程式碼的彙編實現,就能徹底搞清楚了。
可是我不以為然,想當然的篤定這樣的程式碼肯定會編譯失敗。
而且覺得,這樣的可能存在爭議的程式碼,本來就不應該寫出來,如果是我來實現,會在static Month Jan()中增加一個static Month(1)成員,效率高,邏輯清晰。

以至於後來也確實沒有去反彙編這段程式碼,甚至都沒有去驗證是否存在編譯警告問題......
這件事本來就這樣過去了,可是今天繼續翻《effective c++》的時候,又看到了一段程式碼:

class Rational {
    public:
    Rational(int numberator = 0,
                int denominator = 1); //允許隱式轉換
    ...
};
const Rational operator* (const Rational& lhs,
                            const Rational& rhs)
{...}
Rational oneFourth(1, 4);
Rational result;
result = 2 * oneFourth;

明顯的,這裡一樣使用了臨時變數,書上明確寫了一句話: “萬歲,通過編譯了”。
看來這樣做,編譯是可以成功的,而且好像是一種再普通不過的用法。
於是一邊哀嘆自己的淺薄,一邊去查資料搞定這個問題。
終於在c++標準裡找到這樣的解釋:

C++標準對臨時物件的生存期(life time)的規定,可見標準12.2節第3-5條。第3條討論了臨時物件的析構時間:
3 .... Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception.(這其中涉及到的full-expression的定義,參見1.9節,一般指對一個變數的初始化)。
但在第4、5條中,分別討論了兩個例外情形,
1 將臨時物件作為初始化因子,例如string s = string("hello world");
2 將一個常量引用變數繫結到這個臨時物件上。
在這兩種情況中可以通過一個名字來存取這個物件,此物件的生存期就將延長到變數名的作用域結束。除此之外,都按照第3條處理。

這樣解釋之後,一切順理成章。
(原文時間2013-10-1)

相關文章