Có một cuộc tranh luận về việc liệu đây có phải là "hành vi trực quan" trong các bình luận hay không, vì vậy tôi nghĩ rằng tôi sẽ đâm đầu vào lý do đằng sau hành vi này.
Có một cuộc nói chuyện khá hay được đưa ra tại CPPCON, điều này làm cho tôi rõ hơn một chút về { talk , slide }. Về cơ bản, một hàm có tham chiếu không phải là gì? Rằng đối tượng đầu vào phải được đọc / ghi . Thậm chí mạnh hơn, nó ngụ ý tôi có ý định sửa đổi đối tượng này, chức năng này có tác dụng phụ . Một ref ref ngụ ý chỉ đọc và rvalue ref có nghĩa là tôi có thể lấy tài nguyên . Nếu test_1()
cuối cùng gọi cho nhà NON-CONST
xây dựng, điều đó có nghĩa là tôi có ý định sửa đổi đối tượng này, mặc dù sau khi tôi hoàn thành nó không còn tồn tại,mà (tôi nghĩ) sẽ là một lỗi (Tôi đang nghĩ đến một trường hợp trong đó cách tham chiếu bị ràng buộc trong quá trình khởi tạo phụ thuộc vào việc đối số được truyền vào có phải là const hay không).
Điều liên quan hơn một chút với tôi là sự tinh tế được giới thiệu bởi test_2()
. Ở đây, việc khởi tạo danh sách sao chép đang diễn ra thay vì các quy tắc liên quan đến [class.copy.elision] được trích dẫn ở trên. Bây giờ bạn thực sự đang nói trả về một đối tượng thuộc loại MyClass như thể tôi đã khởi tạo nó với buf
, vì vậy NON-CONST
hành vi được gọi. Tôi đã luôn nghĩ về danh sách init là cách ngắn gọn hơn, nhưng ở đây, niềng răng tạo ra sự khác biệt đáng kể về ngữ nghĩa. Điều này sẽ quan trọng hơn nếu các nhà xây dựng MyClass
đã lấy một số lượng lớn các đối số. Sau đó, giả sử bạn muốn tạo một buf
, sửa đổi nó, sau đó trả lại với số lượng lớn các đối số, gọi CONST
hành vi. Ví dụ: giả sử bạn có các hàm tạo:
template <size_t N>
MyClass(const char (&value)[N], int)
{
std::cout << "CONST int " << value << '\n';
}
template <size_t N>
MyClass(char (&value)[N], int)
{
std::cout << "NON-CONST int " << value << '\n';
}
Và kiểm tra:
MyClass test_0() {
char buf[30] = "test_0";
return {buf,0};
}
Godbolt nói với chúng ta rằng chúng ta có NON-CONST
hành vi, mặc dù CONST
có lẽ là những gì chúng ta muốn (sau khi bạn đã uống chất trợ giúp mát mẻ về ngữ nghĩa đối số chức năng). Nhưng bây giờ, việc khởi tạo danh sách bản sao không làm những gì chúng ta muốn. Các loại thử nghiệm sau đây làm cho quan điểm của tôi tốt hơn:
MyClass test_0() {
char buf[30] = "test_0";
buf[0] = 'T';
const char (&bufR)[30]{buf};
return {bufR,0};
}
// OUTPUT: CONST int Test_0
Bây giờ để có được ngữ nghĩa phù hợp với khởi tạo danh sách sao chép, bộ đệm cần phải được "bật lại" ở cuối. Tôi đoán nếu mục tiêu là đối tượng này là để khởi tạo một số MyClass
đối tượng khác , chỉ cần sử dụng NON-CONST
hành vi trong danh sách sao chép trở lại sẽ ổn nếu công cụ di chuyển / sao chép đã gọi bất kỳ hành vi thích hợp nào, nhưng điều đó có vẻ khá hay mong manh.