2012-07-31 17 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(); 
} 

Tôi biết, việc trả lại tham chiếu đến các giá trị cục bộ là xấu. Nhưng, mặt khác, tham chiếu const nên kéo dài tuổi thọ đối tượng tạm thời.Điều gì xảy ra chính xác khi trả về tham chiếu const cho một đối tượng cục bộ?

Mã này tạo ra kết quả UB. Vì vậy, không có sự sống.

Tại sao? Tôi có nghĩa là ai đó có thể giải thích những gì đang xảy ra từng bước?

Lỗi trong chuỗi lý do của tôi ở đâu?

foo():

  1. A (32) - ctor

  2. trở lại A (32) - một tham chiếu const đối tượng địa phương được tạo ra và được trả về

  3. A a = foo(); - a được khởi tạo bởi giá trị trả về foo(), giá trị trả về nằm ngoài phạm vi (ngoài biểu thức) và bị hủy, nhưng đã được khởi tạo;

(Nhưng trên thực tế destructor được gọi là trước khi copy constructor)

foo_2():

  1. trở lại 6 - đối tượng tạm thời loại A được tạo ra ngầm, một tham chiếu const để đối tượng này được tạo (kéo dài tuổi thọ của nó) và được trả lại

  2. A a = foo(); - a được khởi tạo bởi giá trị trả về foo(), giá trị trả về nằm ngoài phạm vi (ngoài biểu thức) và bị hủy, nhưng đã được khởi tạo;

(Nhưng trên thực tế destructor được gọi là trước khi copy constructor)

+1

"tham chiếu const nên kéo dài tuổi thọ đối tượng tạm thời" <- erm, không khi nói đến tham chiếu không const và const đối tượng suốt đời và bằng * không * mở rộng nó. – Giel

+2

Tôi nghĩ đây là những gì Alexander nói về việc kéo dài tuổi thọ: http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ – vmpstr

+1

@Giel: Không. Tham chiếu Const ** có thể ** kéo dài tuổi thọ của đối tượng tạm thời. Tham chiếu Const và non-const khá khác nhau khi nói đến việc làm việc với các thời gian. Trong trường hợp này, nó hoạt động khác với những gì mà OP có vẻ mong đợi. – AnT

Trả lời

11

Quy tắc mở rộng cuộc đời tạm thời đối với từng bối cảnh cụ thể được nêu ra một cách rõ ràng trong đặc tả ngôn ngữ. Và nó nói rằng

12,2 đối tượng tạm thời

5 Bối cảnh thứ hai là khi một tài liệu tham khảo được ràng buộc với một tạm thời. [...] Một ràng buộc tạm thời với giá trị trả về trong câu lệnh return function (6.6.3) vẫn tồn tại cho đến khi hàm thoát. [...]

Đối tượng tạm thời của bạn bị hủy tại thời điểm thoát khỏi chức năng. Điều đó xảy ra trước khi khởi tạo đối tượng người nhận bắt đầu.

Bạn dường như cho rằng tạm thời bằng cách nào đó bạn sẽ sống lâu hơn thế. Rõ ràng bạn đang cố gắng áp dụng quy tắc nói rằng tạm thời nên tồn tại cho đến khi kết thúc biểu hiện đầy đủ. Nhưng quy tắc đó không áp dụng cho các thời gian được tạo bên trong các hàm. Thời gian tồn tại của thời gian như vậy được điều chỉnh bởi các quy tắc riêng của họ.

Cả hai foofoo_2 của bạn tạo ra hành vi không xác định, nếu ai đó cố gắng sử dụng tham chiếu được trả về.

+0

Nhưng nếu "một giới hạn tạm thời với giá trị trả về trong câu lệnh trả về hàm (6.6.3) vẫn tồn tại cho đến khi hàm thoát" giá trị tạm thời shoudnt bị hủy trước khi khởi tạo trong A foo_3() {return A (54);}? – Alexander

+1

@Alexander - Không, 'A foo_3()' trả về một bản sao của giá trị. Giá trị sao chép không bị hủy ở cuối hàm. Khi bạn trả về một tham chiếu, tham chiếu cũng vẫn còn đó - nó chỉ không đề cập đến bất cứ điều gì nữa. –

+0

Tôi thấy .. Nhưng sau đó sao chép ctor nên được gọi là 2 lần trong biểu thức A a = foo_3(); Lần đầu tiên khi trả về giá trị được sao chép từ tạm thời tại địa phương và lần thứ 2 khi khởi tạo A a. Nhưng nó chỉ được gọi một lần. Hoặc nó chỉ là một tối ưu hóa? – Alexander

3

Bạn đang hiểu sai "cho đến khi thoát khỏi chức năng". Nếu bạn thực sự muốn sử dụng một tham chiếu const để kéo dài tuổi thọ của một đối tượng ngoài foo, sử dụng

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

Bạn phải trở về từ foobởi giá trị, và sau đó sử dụng một tham chiếu const để tham khảo các giá trị trả về, nếu bạn muốn mở rộng mọi thứ theo cách bạn mong đợi.

Như @AndreyT đã nói, đối tượng bị hủy trong chức năng có const &. Bạn muốn đối tượng của mình tồn tại ngoài foo và do đó bạn nên khôngconst & (hoặc &) ở bất kỳ đâu trong foo hoặc theo kiểu trả về foo. Đề cập đầu tiên là const & phải ở trong main, vì đó là chức năng nên giữ đối tượng còn sống.

Bạn có thể nghĩ mã trả về theo giá trị này chậm vì có vẻ như là bản sao của A được thực hiện trong lần trả lại nhưng điều này không chính xác. Trong hầu hết các trường hợp, trình biên dịch có thể xây dựng A chỉ một lần, ở vị trí cuối cùng của nó (tức là trên ngăn xếp của hàm gọi), và sau đó thiết lập tham chiếu có liên quan.