2010-03-17 28 views
6

Dưới đây là một đoạn trích từ mục 56 của cuốn sách "C++ Gotchas":C++ sao chép cấu trúc xây dựng-and-assign câu hỏi

Nó không phải là hiếm để thấy một khởi đơn giản của một đối tượng Y viết bất kỳ của ba cách khác nhau, như thể chúng tương đương nhau.

Y a(1066); 
Y b = Y(1066); 
Y c = 1066; 

Trong thực tế, cả ba khởi tạo có thể sẽ dẫn đến trong mã cùng một đối tượng là tạo ra, nhưng chúng không tương đương. Việc khởi tạo một cái được gọi là khởi tạo trực tiếp và nó thực hiện chính xác những gì người ta có thể mong đợi. Việc khởi tạo được thực hiện thông qua một yêu cầu trực tiếp của Y :: Y (int).

Các khởi tạo của b và c là phức tạp hơn. Trên thực tế, chúng quá phức tạp. Đây là cả hai bản sao khởi tạo. Trong trường hợp của việc khởi tạo b, chúng tôi đang yêu cầu việc tạo tạm thời vô danh loại Y, được khởi tạo với giá trị 1066. Sau đó chúng tôi sử dụng thông tin ẩn danh tạm thời này làm tham số cho bản sao hàm tạo cho lớp Y để khởi tạo b. Cuối cùng, chúng tôi gọi hàm hủy là tạm thời ẩn danh.

Để kiểm tra điều này, tôi đã làm một lớp đơn giản với thành viên dữ liệu (chương trình được đính kèm ở cuối) và kết quả thật đáng ngạc nhiên. Dường như đối với trường hợp của c, đối tượng được xây dựng bởi hàm tạo bản sao chứ không phải như được đề xuất trong cuốn sách.

Có ai biết liệu tiêu chuẩn ngôn ngữ đã thay đổi hay đơn giản chỉ là tính năng tối ưu hóa của trình biên dịch? Tôi đã sử dụng Visual Studio 2008.

mẫu Mã số:

#include <iostream> 

class Widget 
{ 
    std::string name; 
public: 
    // Constructor 
    Widget(std::string n) { name=n; std::cout << "Constructing Widget " << this->name << std::endl; } 
    // Copy constructor 
    Widget (const Widget& rhs) { std::cout << "Copy constructing Widget from " << rhs.name << std::endl; } 
    // Assignment operator 
    Widget& operator=(const Widget& rhs) { std::cout << "Assigning Widget from " << rhs.name << " to " << this->name << std::endl; return *this; } 
}; 

int main(void) 
{ 
    // construct 
    Widget a("a"); 
    // copy construct 
    Widget b(a); 
    // construct and assign 
    Widget c("c"); 
    c = a; 
    // copy construct! 
    Widget d = a; 
    // construct! 
    Widget e = "e"; 
    // construct and assign 
    Widget f = Widget("f"); 

    return 0; 
} 

Output:

Constructing Widget a 

Copy constructing Widget from a 

Constructing Widget c 
Assigning Widget from a to c 

Copy constructing Widget from a 

Constructing Widget e 

Constructing Widget f 
Copy constructing Widget from f 

Tôi đã rất ngạc nhiên nhất bởi kết quả xây dựng d và e. Để được chính xác, tôi đã mong đợi một đối tượng trống được tạo ra, và sau đó một đối tượng được tạo ra và gán cho đối tượng trống. Trong thực tế, các đối tượng đã được tạo bởi trình tạo bản sao.

+0

"Trong trường hợp khởi tạo b, chúng tôi yêu cầu tạo tạm thời ẩn danh loại Y, được khởi tạo với giá trị 1066" ... và giống như trong khởi tạo của c, nó khó xem hơn tạm thời. –

+0

Lưu ý rằng 'Y c = 1006' là không thể nếu hàm tạo của bạn được khai báo là' tường minh' ... như bất kỳ hàm tạo tham số nào, hầu hết thời gian. –

+0

Matthieu, vâng tôi đồng ý với những gì bạn nói và tôi luôn làm điều đó khi lập trình. Điểm của bạn cũng được đề cập trong cuốn sách "Hiệu quả hơn C++" nếu tôi nhớ chính xác. – Andy

Trả lời

16

Cú pháp

X a = b; 

nơi a và b là loại X đã luôn luôn có nghĩa là xây dựng bản sao.Bất kể biến thể nào, chẳng hạn như:

X a = X(); 

được sử dụng, không có chuyển nhượng nào đang diễn ra và chưa từng có. Xây dựng và phân công sẽ là một cái gì đó như:

X a; 
a = X(); 
+0

Cảm ơn nhận xét về xây dựng bản sao. Tôi đã không thực sự nhận thức được điều đó - tôi luôn nghĩ rằng bạn phải làm X = a (b); để chắc chắn gọi trình tạo bản sao. – Andy

6

Trình biên dịch được phép tối ưu hóa các trường hợp bc tương tự như a. Hơn nữa, sao chép xây dựng và chuyển nhượng các cuộc gọi nhà điều hành có thể được loại bỏ hoàn toàn bởi trình biên dịch anyway, vì vậy bất cứ điều gì bạn thấy không nhất thiết phải giống nhau với trình biên dịch khác nhau hoặc thậm chí cài đặt trình biên dịch.

+0

Vì vậy, điều này có nghĩa là sách có thể không chính xác 100% ngay bây giờ? Hấp dẫn. – Andy

+4

@Andy, không có sách nào chính xác 100%. Nhưng nó không hoàn thành 100%. Nếu bạn muốn hoàn thành 100%, bạn cần tham khảo tiêu chuẩn. Cụ thể, nó (hoặc phần bạn trích dẫn) không giải thích rằng trình biên dịch được phép tối ưu hóa các bản sao nếu việc sao chép được thực hiện từ tạm thời như trên. –

+0

Cảm ơn bạn. Tôi đoán bạn vẫn nên sử dụng các định dạng được đề xuất bởi cuốn sách, trong trường hợp trình biên dịch bạn đang sử dụng không thực hiện tối ưu hóa. – Andy

1

Tính đến C++ 17, cả ba tương đương (trừ khi Y::Y(int)explicit, mà chỉ đơn giản sẽ không cho phép c) vì những gì thường được gọi là mandatory copy elision.

Thậm chí Y c = 1066; tạo ra chỉ có một đối tượng Y vì kết quả của việc chuyển đổi ngầm để Y là một prvalue được sử dụng để khởi tạo c chứ không phải là để tạo ra một tạm thời.