Cập nhật C ++ 17
Trong C ++ 17, ý nghĩa của việc A_factory_func()
thay đổi từ việc tạo một đối tượng tạm thời (C ++ <= 14) sang chỉ định khởi tạo bất kỳ đối tượng nào mà biểu thức này được khởi tạo thành (nói một cách lỏng lẻo) trong C ++ 17. Các đối tượng này (được gọi là "đối tượng kết quả") là các biến được tạo bởi một khai báo (như a1
), các đối tượng nhân tạo được tạo khi khởi tạo kết thúc bị loại bỏ hoặc nếu một đối tượng là cần thiết cho liên kết tham chiếu (như, trong A_factory_func();
trường hợp cuối cùng, một đối tượng được tạo ra một cách giả tạo, được gọi là "vật chất hóa tạm thời", bởi vì A_factory_func()
không có biến hoặc tham chiếu mà nếu không sẽ yêu cầu một đối tượng tồn tại).
Như các ví dụ trong trường hợp của chúng tôi, trong trường hợp a1
và a2
các quy tắc đặc biệt nói rằng trong các khai báo như vậy, đối tượng kết quả của trình khởi tạo prvalue cùng loại với a1
biến a1
và do đó A_factory_func()
trực tiếp khởi tạo đối tượng a1
. Bất kỳ diễn viên kiểu chức năng trung gian nào cũng sẽ không có bất kỳ ảnh hưởng nào, bởi vì A_factory_func(another-prvalue)
chỉ "đi qua" đối tượng kết quả của giá trị bên ngoài cũng là đối tượng kết quả của giá trị bên trong.
A a1 = A_factory_func();
A a2(A_factory_func());
Phụ thuộc vào loại A_factory_func()
trả về. Tôi giả sử nó trả về một A
- sau đó nó cũng làm như vậy - ngoại trừ khi hàm tạo sao chép rõ ràng, thì cái đầu tiên sẽ thất bại. Đọc 8,6 / 14
double b1 = 0.5;
double b2(0.5);
Điều này cũng tương tự vì đây là loại tích hợp (điều này có nghĩa không phải là loại lớp ở đây). Đọc 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
Điều này không làm như vậy. Mặc định đầu tiên - khởi tạo nếu A
không phải là POD và không thực hiện bất kỳ khởi tạo nào cho POD (Đọc 8.6 / 9 ). Bản sao thứ hai khởi tạo: Giá trị - khởi tạo tạm thời và sau đó sao chép giá trị đó vào c2
(Đọc 5.2.3 / 2 và 8.6 / 14 ). Điều này tất nhiên sẽ yêu cầu một hàm tạo sao chép không rõ ràng (Đọc 8.6 / 14 và 12.3.1 / 3 và 13.3.1.3/1 ). Cái thứ ba tạo ra một khai báo hàm cho một hàm c3
trả về một A
và đưa một con trỏ hàm đến một hàm trả về một A
(Đọc 8.2 ).
Đi sâu vào khởi tạo Khởi tạo trực tiếp và sao chép
Mặc dù trông giống hệt nhau và được cho là làm giống nhau, hai hình thức này khác nhau đáng kể trong một số trường hợp nhất định. Hai hình thức khởi tạo là khởi tạo trực tiếp và sao chép:
T t(x);
T t = x;
Có những hành vi chúng ta có thể quy cho mỗi người trong số họ:
- Khởi tạo trực tiếp hoạt động giống như một hàm gọi đến một hàm bị quá tải: Các hàm, trong trường hợp này, là các hàm tạo của
T
(bao gồm cả explicit
các hàm) và đối số là x
. Độ phân giải quá tải sẽ tìm ra hàm tạo phù hợp nhất và khi cần sẽ thực hiện bất kỳ chuyển đổi ngầm định nào được yêu cầu.
- Sao chép khởi tạo xây dựng một chuỗi chuyển đổi ngầm định: Nó cố gắng chuyển đổi
x
thành một đối tượng kiểu T
. (Sau đó, nó có thể sao chép đối tượng đó vào đối tượng được khởi tạo, do đó cũng cần một hàm tạo sao chép - nhưng điều này không quan trọng dưới đây)
Như bạn thấy, khởi tạo sao chép theo một cách nào đó là một phần của khởi tạo trực tiếp liên quan đến chuyển đổi ngầm có thể có: Trong khi khởi tạo trực tiếp có tất cả các hàm tạo có sẵn để gọi và ngoài ra có thể thực hiện bất kỳ chuyển đổi ngầm nào để khớp với các loại đối số, sao chép khởi tạo chỉ có thể thiết lập một chuỗi chuyển đổi ngầm định.
Tôi đã cố gắng hết sức và nhận được đoạn mã sau để xuất văn bản khác nhau cho mỗi biểu mẫu đó mà không sử dụng "hiển nhiên" thông qua các hàm explicit
tạo.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
Làm thế nào nó hoạt động, và tại sao nó xuất kết quả đó?
Khởi tạo trực tiếp
Đầu tiên nó không biết gì về chuyển đổi. Nó sẽ chỉ cố gắng gọi một nhà xây dựng. Trong trường hợp này, hàm tạo sau có sẵn và khớp chính xác :
B(A const&)
Không có chuyển đổi, ít hơn một chuyển đổi do người dùng xác định, cần thiết để gọi hàm tạo đó (lưu ý rằng không có chuyển đổi định tính const nào xảy ra ở đây). Và vì vậy, khởi tạo trực tiếp sẽ gọi nó.
Sao chép khởi tạo
Như đã nói ở trên, khởi tạo sao chép sẽ xây dựng một chuỗi chuyển đổi khi a
chưa gõ B
hoặc xuất phát từ nó (rõ ràng là trường hợp ở đây). Vì vậy, nó sẽ tìm cách để thực hiện chuyển đổi và sẽ tìm thấy các ứng cử viên sau đây
B(A const&)
operator B(A&);
Lưu ý cách tôi viết lại hàm chuyển đổi: Kiểu tham số phản ánh loại this
con trỏ, trong hàm không phải là thành viên không phải là hằng. Bây giờ, chúng tôi gọi những ứng cử viên này x
là đối số. Người chiến thắng là chức năng chuyển đổi: Bởi vì nếu chúng ta có hai hàm ứng cử viên, cả hai đều chấp nhận tham chiếu đến cùng loại, thì phiên bản const ít hơn sẽ thắng (đây cũng là cơ chế ưu tiên các hàm không phải là thành viên không gọi đối tượng -const).
Lưu ý rằng nếu chúng ta thay đổi hàm chuyển đổi thành hàm const thành viên, thì chuyển đổi không rõ ràng (vì cả hai đều có kiểu tham số A const&
sau đó): Trình biên dịch Comeau từ chối nó một cách chính xác, nhưng GCC chấp nhận nó ở chế độ không phạm vi. Tuy nhiên, chuyển sang -pedantic
làm cho nó đưa ra cảnh báo mơ hồ thích hợp.
Tôi hy vọng điều này sẽ giúp phần nào làm cho nó rõ ràng hơn về hai hình thức này khác nhau như thế nào!
A c1; A c2 = c1; A c3(c1);
.