Có một thành viên lớp tham chiếu const kéo dài cuộc sống của một tạm thời?


171

Tại sao điều này:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

Cho đầu ra của:

Câu trả lời là:

Thay vì:

Câu trả lời là: bốn


39
Và chỉ để vui hơn, nếu bạn đã viết cout << "The answer is: " << Sandbox(string("four")).member << endl;, thì nó sẽ được đảm bảo để làm việc.

7
@RogerPate Bạn có thể giải thích tại sao?
Paolo M

16
Đối với ai đó tò mò, ví dụ Roger Pate đã đăng các tác phẩm vì chuỗi ("bốn") là tạm thời và tạm thời bị phá hủy khi kết thúc toàn bộ biểu hiện , vì vậy trong ví dụ của anh ấy khi SandBox::memberđược đọc, chuỗi tạm thời vẫn còn sống .
PcAF

1
Câu hỏi đặt ra là: Vì việc viết các lớp như vậy là nguy hiểm, có cảnh báo trình biên dịch nào chống lại việc chuyển tạm thời sang các lớp đó không , hoặc có một hướng dẫn thiết kế (trong Stroustroup không?) Cấm các lớp viết lưu trữ tài liệu tham khảo? Một hướng dẫn thiết kế để lưu trữ con trỏ thay vì tài liệu tham khảo sẽ tốt hơn.
Grim Fandango

@PcAF: Bạn có thể giải thích lý do tại sao tạm thời string("four")bị hủy ở cuối biểu thức đầy đủ mà không phải sau khi hàm Sandboxtạo thoát ra không? Câu trả lời của Potatoswatter cho biết Một ràng buộc tạm thời với một thành viên tham chiếu trong trình khởi tạo ctor của trình xây dựng (§12.6.2 [class.base.init]) vẫn tồn tại cho đến khi hàm tạo thoát ra.
Taylor Nichols

Câu trả lời:


166

Chỉ tham khảo địa phương const kéo dài tuổi thọ.

Tiêu chuẩn quy định hành vi như vậy trong §8.5.3 / 5, [dcl.init.ref], phần khởi tạo khai báo tham chiếu. Tham chiếu trong ví dụ của bạn bị ràng buộc với đối số của hàm tạo nvà trở nên không hợp lệ khi đối tượng nbị ràng buộc đi ra khỏi phạm vi.

Phần mở rộng trọn đời không phải là bắc cầu thông qua một đối số chức năng. §12.2 / 5 [class.t tạm thời]:

Bối cảnh thứ hai là khi một tham chiếu bị ràng buộc tạm thời. Tạm thời mà tham chiếu bị ràng buộc hoặc tạm thời là đối tượng hoàn chỉnh cho một tiểu dự án mà tạm thời bị ràng buộc tồn tại trong suốt thời gian tham chiếu trừ khi được chỉ định dưới đây. Một ràng buộc tạm thời với một thành viên tham chiếu trong trình khởi tạo ctor của trình xây dựng (§12.6.2 [class.base.init]) vẫn tồn tại cho đến khi hàm tạo thoát ra. Một ràng buộc tạm thời với một tham số tham chiếu trong lệnh gọi hàm (§5.2.2 [expr.call]) vẫn tồn tại cho đến khi hoàn thành biểu thức đầy đủ chứa lệnh gọi.


49
Bạn cũng nên xem GotW # 88 để có lời giải thích thân thiện với con người hơn: Herbutter.com/2008/01/01/iêu
Nathan Ernst

1
Tôi nghĩ sẽ rõ ràng hơn nếu tiêu chuẩn nói "Bối cảnh thứ hai là khi một tham chiếu bị ràng buộc với một giá trị". Trong mã của OP, bạn có thể nói rằng memberbị ràng buộc tạm thời, bởi vì việc khởi tạo memberbằng các nphương tiện để liên kết membervới cùng một đối tượng nbị ràng buộc và trên thực tế đó là một đối tượng tạm thời trong trường hợp này.
MM

2
@MM Có những trường hợp khởi tạo lvalue hoặc xvalue chứa prvalue sẽ mở rộng giá trị. Giấy đề xuất của tôi P0066 xem xét tình trạng.
Potatoswatter

1
Kể từ C ++ 11, các tham chiếu Rvalue cũng kéo dài tuổi thọ tạm thời mà không yêu cầu constquaifier.
GetFree

3
@KeNVinFavo có, sử dụng một vật thể chết luôn là UB
Potatoswatter

30

Đây là cách đơn giản nhất để giải thích những gì đã xảy ra:

Trong hàm main () bạn đã tạo một chuỗi và chuyển nó vào hàm tạo. Ví dụ chuỗi này chỉ tồn tại trong hàm tạo. Bên trong hàm tạo, bạn đã chỉ định thành viên trỏ trực tiếp vào trường hợp này. Khi phạm vi rời khỏi hàm tạo, thể hiện chuỗi đã bị hủy và thành viên sau đó chỉ vào một đối tượng chuỗi không còn tồn tại. Có Sandbox.member trỏ đến một tham chiếu ngoài phạm vi của nó sẽ không giữ các trường hợp bên ngoài đó trong phạm vi.

Nếu bạn muốn sửa chương trình của mình để hiển thị hành vi bạn muốn, hãy thực hiện các thay đổi sau:

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

Bây giờ temp sẽ vượt ra khỏi phạm vi ở cuối hàm main () thay vì ở cuối hàm tạo. Tuy nhiên, đây là thực tế xấu. Biến thành viên của bạn không bao giờ nên là một tham chiếu đến một biến tồn tại bên ngoài thể hiện. Trong thực tế, bạn không bao giờ biết khi nào biến đó sẽ đi ra khỏi phạm vi.

Điều tôi khuyên là nên định nghĩa Sandbox.member là const string member;Điều này sẽ sao chép dữ liệu của tham số tạm thời vào biến thành viên thay vì chỉ định biến thành viên là chính tham số tạm thời.


Nếu tôi làm điều này: const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;nó sẽ vẫn hoạt động chứ?
Yves

@Thomas const string &temp = string("four");cho kết quả tương tự const string temp("four"); , trừ khi bạn sử dụng decltype(temp)cụ thể
MM

@MM Cảm ơn rất nhiều bây giờ tôi hoàn toàn hiểu câu hỏi này.
Yves

However, this is bad practice.- tại sao? Nếu cả temp và đối tượng chứa đều sử dụng lưu trữ tự động trong cùng một phạm vi, nó có an toàn 100% không? Và nếu bạn không làm điều đó, bạn sẽ làm gì nếu chuỗi quá lớn và quá đắt để sao chép?
tối đa

2
@max, vì lớp không thi hành tạm thời thông qua để có phạm vi chính xác. Điều đó có nghĩa là một ngày nào đó bạn có thể quên yêu cầu này, vượt qua giá trị tạm thời không hợp lệ và trình biên dịch sẽ không cảnh báo bạn.
Alex Che

5

Về mặt kỹ thuật, chương trình này không bắt buộc phải thực sự xuất bất cứ thứ gì thành đầu ra tiêu chuẩn (là luồng được đệm để bắt đầu).

  • Các cout << "The answer is: "bit sẽ phát ra "The answer is: "vào bộ đệm của thiết bị xuất chuẩn.

  • Sau đó, << sandbox.memberbit sẽ cung cấp tham chiếu lơ lửng vào operator << (ostream &, const std::string &), gọi ra hành vi không xác định .

Bởi vì điều này, không có gì được đảm bảo xảy ra. Chương trình có thể hoạt động có vẻ tốt hoặc có thể bị sập mà thậm chí không xuất hiện thiết bị xuất chuẩn - có nghĩa là văn bản "Câu trả lời là:" sẽ không xuất hiện trên màn hình của bạn.


2
Khi có UB, toàn bộ hành vi của chương trình không được xác định - nó không chỉ bắt đầu tại một điểm cụ thể trong quá trình thực thi. Vì vậy, chúng tôi không thể nói chắc chắn rằng nó "The answer is: "sẽ được viết ở bất cứ đâu.
Toby Speight

0

Bởi vì chuỗi tạm thời của bạn đã vượt ra khỏi phạm vi một khi trình xây dựng Sandbox quay trở lại và ngăn xếp bị chiếm bởi nó được lấy lại cho một số mục đích khác.

Nói chung, bạn không bao giờ nên giữ lại tài liệu tham khảo lâu dài. Tài liệu tham khảo tốt cho các đối số hoặc biến cục bộ, không bao giờ là thành viên lớp.


7
"Không bao giờ" là một từ cực kỳ mạnh mẽ.
Fred Larson

17
không bao giờ thành viên lớp trừ khi bạn cần giữ một tham chiếu đến một đối tượng. Có những trường hợp bạn cần giữ các tham chiếu đến các đối tượng khác chứ không phải các bản sao, đối với những trường hợp đó, các tham chiếu là một giải pháp rõ ràng hơn các con trỏ.
David Rodríguez - dribeas

0

bạn đang đề cập đến một cái gì đó đã biến mất. Sau đây sẽ làm việc

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.