Thời gian tồn tại của các đối tượng tạm thời bị ràng buộc vào tham chiếu được kéo dài, trừ khi có một ngoại lệ cụ thể. Đó là, nếu không có ngoại lệ như vậy, sau đó cuộc đời sẽ được mở rộng.
Từ một dự thảo khá gần đây, N4567:
Bối cảnh thứ hai [nơi tuổi thọ là mở rộng] là khi một tài liệu tham khảo được ràng buộc với một tạm thời. Tạm thời mà tham chiếu là ràng buộc hoặc tạm thời mà là đối tượng hoàn toàn của một subobject mà tham chiếu được ràng buộc kéo dài tuổi thọ của tham chiếu trừ:
- (5,1) Một đối tượng tạm thời bị ràng buộc tham số tham chiếu trong cuộc gọi hàm (5.2.2) vẫn tồn tại cho đến khi hoàn thành biểu thức đầy đủ chứa cuộc gọi.
- (5.2) Thời gian tồn tại của 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) không được kéo dài; tạm thời là bị hủy ở cuối biểu thức đầy đủ trong câu lệnh trả về.
- (5.3) Một giới hạn tạm thời với tham chiếu trong bộ khởi tạo mới (5.3.4) vẫn tồn tại cho đến khi hoàn thành biểu thức đầy đủ chứa bộ khởi tạo mới.
Sự thay đổi đáng kể duy nhất để C++ 11 là, như OP đã đề cập, rằng trong C++ 11 đã có một ngoại lệ bổ sung cho dữ liệu thành viên của các loại tài liệu tham khảo (từ N3337):
- Một giới hạn tạm thời cho một thành viên tham chiếu trong bộ khởi tạo của hàm khởi tạo (12.6.2) vẫn tồn tại cho đến khi hàm khởi tạo xuất hiện.
này đã bị xóa trong CWG 1696 (post-C++ 14), và ràng buộc đối tượng tạm thời để tham khảo các thành viên dữ liệu thông qua các mem-initializer bây giờ đã vô hình thành.
Về các ví dụ trong OP:
struct S
{
const std::string& str_;
};
S a{"foo"}; // direct-initialization
này tạo ra một tạm thời std::string
và khởi tạo các thành viên str_
dữ liệu với nó. Các S a{"foo"}
sử dụng tổng hợp-initialization, do đó, không có mem-initializer có liên quan. Không có ngoại lệ nào cho các tiện ích mở rộng lâu dài được áp dụng, do đó thời gian tồn tại của thời gian đó được kéo dài đến tuổi thọ của thành viên dữ liệu tham chiếu str_
.
auto b = S{"bar"}; // copy-initialization with rvalue
Trước sự bỏ bớt bản bắt buộc với C++ 17: Chính thức, chúng ta tạo ra một tạm thời std::string
, khởi tạo một tạm thời S
bằng cách liên kết tạm thời std::string
cho thành viên str_
tài liệu tham khảo . Sau đó, chúng tôi di chuyển tạm thời S
đó thành b
. Thao tác này sẽ "sao chép" tham chiếu, thao tác này sẽ không kéo dài tuổi thọ của std::string
tạm thời. Tuy nhiên, việc triển khai sẽ di chuyển di chuyển từ tạm thời S
sang b
. Tuy nhiên, điều này không ảnh hưởng đến toàn bộ thời gian tạm thời của std::string
. Bạn có thể quan sát này trong chương trình sau:
#include <iostream>
#define PRINT_FUNC() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
struct loud
{
loud() PRINT_FUNC()
loud(loud const&) PRINT_FUNC()
loud(loud&&) PRINT_FUNC()
~loud() PRINT_FUNC()
};
struct aggr
{
loud const& l;
~aggr() PRINT_FUNC()
};
int main() {
auto x = aggr{loud{}};
std::cout << "end of main\n";
(void)x;
}
Live demo
Lưu ý rằng destructor của loud
được gọi trước khi "kết thúc của chính", trong khi x
cuộc sống cho đến sau dấu vết đó. Chính thức, tạm thời loud
bị hủy ở cuối biểu thức đầy đủ đã tạo ra nó.
Hành vi không thay đổi nếu hàm tạo di chuyển của aggr
được người dùng xác định.
Với bắt buộc sao chép sự bỏ bớt trong C++ 17: Chúng tôi xác định các đối tượng trên RHS S{"bar"}
với các đối tượng trên LHS b
. Điều này làm cho toàn bộ thời gian tạm thời được kéo dài đến tuổi thọ của b
. Xem CWG 1697.
Đối với hai ví dụ còn lại, hàm tạo di chuyển - nếu được gọi - chỉ cần sao chép tham chiếu. Dĩ nhiên, hàm tạo di chuyển (của S
) có thể được elided, nhưng điều này không thể quan sát được vì nó chỉ sao chép tham chiếu.
Không có ngoại lệ nào từ phần mở rộng thời gian của thời gian chờ để khởi tạo tổng hợp, do đó toàn bộ thời gian tạm thời sẽ được mở rộng. Điều này đảm bảo một đời thích hợp cho việc tạo tạm thời trong trường hợp "khởi tạo trực tiếp". – dyp
ý của bạn là "di chuyển có thể được elided"? Tham chiếu ràng buộc không thể được elided, 'str_' ràng buộc trực tiếp đến' other.str'. ('std :: move' không có hiệu lực) –
@ M.M Tôi có nghĩa là hầu hết các trình biên dịch sẽ thực hiện khởi tạo trực tiếp thay vì sử dụng hàm tạo di chuyển. Có 'std :: move (khác) .str' giống như' other.str' cho tham chiếu và không có hiệu lực ở đây – 3XX0