2010-11-11 23 views
35

Câu hỏi gần đây khiến tôi băn khoăn về các nhà xây dựng bản sao rõ ràng. Dưới đây là một số mẫu mã mà tôi đã cố gắng biên soạn theo Visual Studio 2005:Hành vi tạo bản sao rõ ràng và sử dụng thực tế

struct A 
{ 
    A() {} 
    explicit A(const A &) {} 
}; 

// #1 > Compilation error (expected behavior) 
A retByValue() 
{ 
    return A(); 
} 

// #2 > Compiles just fine, but why ? 
void passByValue(A a) 
{ 
} 

int main() 
{ 
    A a; 
    A b(a); // #3 > explicit copy construction : OK (expected behavior) 
    A c = a; // #4 > implicit copy construction : KO (expected behavior) 

    // Added after multiple comments : not an error according to VS 2005. 
    passByValue(a); 
    return 0; 
} 

Bây giờ cho các câu hỏi sau:

  • là # 2 cho phép theo tiêu chuẩn? Nếu có, phần liên quan mô tả tình huống này là gì?
  • Bạn có biết bất kỳ cách sử dụng thực tế nào cho một nhà xây dựng bản sao rõ ràng không?

[EDIT] tôi chỉ tìm thấy một liên kết hài hước trên MSDN với tình hình chính xác giống nhau, và một bình luận bí ẩn từ các chức năng chính: "c được sao chép" (như thể nó là rõ ràng). Theo chỉ dẫn của Oli Charlesworth: gcc không biên dịch mã này và tôi tin rằng anh ta không đúng.

+0

Tôi không nghĩ rằng các nhà xây dựng bản sao rõ ràng là một ý tưởng hay. Bạn đã đọc về chúng ở đâu? – fredoverflow

+1

Điều này dường như được sửa trong VC++ 2010 - nó đưa ra một lỗi cho dòng 'passByValue (a); '. – user200783

Trả lời

39

tôi tin rằng các bộ phận liên quan của C++ 03 là §12.3.1 2:

An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9 , 5.4) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value-initialization (8.5).

§ 8.5 12:

The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form

T x = a; 

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form

T x(a); 

Calling passByValue(a) liên quan đến việc sao chép khởi, không trực tiếp khởi tạo, và do đó phải là một lỗi, theo C++ 03 § 12.3.1 2.

+2

Mặc dù các câu trả lời của MSalters và Oli là chính xác, tôi sẽ chấp nhận câu trả lời này vì nó làm rõ rằng VS sai. Cảm ơn ! – icecrime

+0

Câu trả lời rất hay, cảm ơn. – ttvd

4

Định nghĩa của passByValue là OK, bởi vì không có tuyên bố sao chép đối tượng A. Trong định nghĩa của retByValue, dĩ nhiên có một câu lệnh trả về sao chép một đối tượng A.

+1

Tôi không chắc tôi hiểu câu trả lời của bạn.Chỉ cần được rõ ràng: Tôi giữ mã ở mức tối thiểu, nhưng nó là ok cho trình biên dịch để gọi 'passByValue (A())' từ chính, mà tôi tin rằng đòi hỏi phải sao chép ngầm. – icecrime

+1

@icecrime: Không, bạn không thể gọi 'passByValue()' như vậy ... –

+0

Sự hiểu biết của tôi về điều này là đối tượng 'a' không bao giờ được sử dụng trong' passByValue() ', do đó trình biên dịch bỏ qua nó. – djeidot

2

Để mở rộng câu trả lời @MSalters (chính xác), nếu bạn thêm passByValue(a); vào hàm main(), trình biên dịch sẽ nên khiếu nại về điều đó.

Trình tạo bản sao rõ ràng là để ngăn chặn chính xác điều này, tức là ngăn chặn sao chép ngầm tài nguyên trong các cuộc gọi hàm và vv (về cơ bản nó buộc người dùng chuyển qua tham chiếu thay vì truyền theo giá trị).

+0

Không, nó sẽ không, passByValue (a); trong main() biên dịch tốt, và tôi, như icecrime, tin rằng có một bản sao ẩn của đối tượng. – davidnr

+0

@davidnr, hmm, không thành công trong gcc do lý do được đề cập ở trên. – Nim

+0

@davidnr: Theo GCC, tôi nhận được 'lỗi: không có hàm nào phù hợp để gọi 'A :: A (A &)',' lỗi: khởi tạo đối số 1 của 'void passByValue (A)' ' –

3

Sử dụng thực tế, trước C++ 11, để tạo ra một hàm tạo bản sao trong trường hợp nó thực sự là một phần của việc làm cho một lớp không thể sao chép được.

Nguy hiểm là, mặc dù bạn khai báo hàm tạo bản sao riêng tư và không triển khai nó, nếu bạn vô tình sao chép một trong một người bạn hoặc trong lớp, trình biên dịch sẽ không nhặt nó lên và bạn ' sẽ chỉ gặp lỗi liên kết khó tìm.

Làm cho nó rõ ràng cũng như cắt giảm cơ hội này khi trình biên dịch cũng có thể lấy bản sao không chủ ý của bạn và trỏ đến dòng thực tế nơi bạn đang làm nó.

Trong C++ 11 (và 14) không cần thực hiện điều này khi sử dụng cú pháp =delete vì bạn sẽ gặp lỗi trình biên dịch ngay cả khi sao chép trong chính lớp đó hoặc trong một người bạn.

Các vấn đề liên quan