2012-07-31 80 views
5
struct A { 
    A(int) : i(new int(783)) { 
     std::cout << "a ctor" << std::endl; 
    } 

    A(const A& other) : i(new int(*(other.i))) { 
     std::cout << "a copy ctor" << std::endl; 
    } 

    ~A() { 
     std::cout << "a dtor" << std::endl; 
     delete i; 
    } 

    void get() { 
     std::cout << *i << std::endl; 
    } 

private: 
    int* i; 
}; 

const A& foo() { 
    return A(32); 
} 

const A& foo_2() { 
    return 6; 
} 

int main() 
{ 
    A a = foo(); 
    a.get(); 
} 

我知道,返回對本地值的引用是不好的。但是,另一方面,const引用應該擴展一個臨時對象的生命週期。將const引用返回給本地對象時發生了什麼?

此代碼產生一個UB輸出。所以沒有生命延伸。

爲什麼?我的意思是可以有人解釋一步一步發生什麼?

我的推理鏈中哪裏出錯?

FOO():

  1. A(32) - 構造函數

  2. 返回A(32) - 一個常量引用本地對象創建並返回

  3. A中的= foo(); - a由foo()返回值初始化,返回值超出範圍(超出表達式)並被銷燬,但a已經初始化;

(但實際上析構函數拷貝構造函數調用之前)

FOO_2():

  1. 返回6 - A型的臨時對象是隱式創建,const引用到這個對象(延長其壽命)並返回

  2. A a = foo(); - a由foo()返回值初始化,返回值超出範圍(超出表達式)並被銷燬,但a已經初始化;

(但實際上析構函數拷貝構造函數調用之前)

+1

「const引用應該擴展一個臨時對象的生命週期」< - erm,當涉及到對象生命週期非const並且const引用相等並且不*擴展它時。 – Giel 2012-07-31 19:36:47

+2

我認爲這是亞歷山大談到延長生命的意義:http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ – vmpstr 2012-07-31 19:44:31

+1

@Giel:沒有。Const參考**可以**延長臨時對象的生命週期。在處理臨時對象時,常量和非常量引用是完全不同的。在這種情況下,它的作用與OP所期望的不同。 – AnT 2012-07-31 19:45:04

回答

11

臨時壽命延長爲每個特定背景下的規則在語言規範中明確規定了。和它說,

12.2臨時對象

5的第二上下文是當引用綁定到一個暫時的。 [...]函數返回語句 (6.6.3)中返回值的臨時綁定一直存在,直到函數退出。 [...]

您的臨時對象在函數退出時被銷燬。在收件人對象的初始化開始之前發生這種情況。

你似乎認爲你的臨時應該以某種方式活得比那更長。顯然你正試圖應用這個規則,即臨時應該存在直到完整表達式的結尾。但是該規則不適用於在函數內部創建的臨時對象。這種臨時工的生命週期由他們自己的專用規則來管理。

如果有人試圖使用返回的引用,則foofoo_2都會產生未定義的行爲。

+0

但是,如果「函數返回語句(6.6.3)中的返回值的臨時綁定一直存在,直到函數退出」shoudnt臨時值在初始化之前在A foo_3(){return A(54);}中被銷燬? – Alexander 2012-07-31 19:53:42

+1

@Alexander - 不,''foo_3()'返回值的一個副本。複製的值在函數結束時不會被銷燬。當你返回一個參考文獻時,參考文獻仍然存在 - 它只是不再提及任何東西。 – 2012-07-31 20:08:12

+0

我明白了..但是,然後複製ctor應該在表達式中調用2次A a = foo_3();第一次當初始化A a時從臨時本地和第二次複製值複製。但它只叫一次。或者它只是一個優化? – Alexander 2012-07-31 20:13:53

3

你錯了「直到函數退出」。如果你真的想用const引用來擴展對象的生命超越foo,使用

A foo() { 
    return A(32); 
} 
int main() { 
    const A& a = foo(); 
} 

您必須通過值從foo返回,然後用const引用來引用的返回值,如果你希望以你期望的方式擴展事物。

正如@AndreyT所說,該對象在具有const &的函數中被銷燬。您希望您的對象超出foo生存,因此你應該const & (或&)在foo或在foo返回類型的任何地方。 const &的第一個提到應該在main,因爲這是應該保持對象活着的功能。

您可能認爲這種按價值計算返回的代碼很慢,因爲在返回中似乎存在A副本,但這是不正確的。在大多數情況下,編譯器只能在其最終位置(即調用函數的堆棧中)構造A一次,然後設置相關引用。

相關問題