2009-12-01 2 views
10

Đoạn mã sau chỉ hoạt động khi có sẵn hàm tạo bản sao.Sao chép Constructor Cần thiết với đối tượng tạm thời

Khi tôi thêm báo cáo in (qua std::cout) và làm cho trình tạo bản sao có sẵn thì không được sử dụng (tôi cho rằng có quá trình biên dịch xảy ra để xóa bản sao không cần thiết).

Nhưng ở cả đầu ra operator << và hàm plop() bên dưới (nơi tôi tạo đối tượng tạm thời), tôi không thấy sự cần thiết của hàm tạo bản sao. Ai đó có thể giải thích lý do tại sao ngôn ngữ cần nó khi tôi đi qua tất cả mọi thứ bằng tham chiếu const (hoặc những gì tôi đang làm sai).

#include <iostream> 

class N 
{ 
    public: 
     N(int) {} 
    private: 
     N(N const&); 
}; 

std::ostream& operator<<(std::ostream& str,N const& data) 
{ 
    return str << "N\n"; 
} 

void plop(std::ostream& str,N const& data) 
{ 
    str << "N\n"; 
} 

int main() 
{ 
    std::cout << N(1);  // Needs copy constructor (line 25) 
    plop(std::cout,N(1)); // Needs copy constructor 

    N a(5); 
    std::cout << a; 
    plop(std::cout,a); 
} 

Compiler:

[Alpha:~/X] myork% g++ -v
Using built-in specs.
Target: i686-apple-darwin10
Configured with: /var/tmp/gcc/gcc-5646~6/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10
Thread model: posix
gcc version 4.2.1 (Apple Inc. build 5646)

[Alpha:~/X] myork% g++ t.cpp
t.cpp: In function ‘int main()’:
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:25: error: within this context
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:26: error: within this context

Đây là một phiên bản đơn giản của một số mã thực.
Trong mã thực, tôi có một lớp có chứa tiêu chuẩn :: auto_ptr. Điều này có nghĩa rằng một constructor sao chép mà phải mất một tham chiếu const là không hợp lệ (không có một số công việc) và tôi đã nhận được một lỗi chỉ ra rằng các nhà xây dựng bản sao đã không có sẵn vì nó:

Thay đổi lớp quá:

class N 
{ 
    public: 
     N(int) {} 
    private: 
     std::auto_ptr<int> data; 
}; 

lỗi này là sau đó:

t.cpp:25: error: no matching function for call to ‘N::N(N)’

+3

trình biên dịch nào? Điều này biên dịch tốt trên VC9 – Naveen

+0

N (N const &) phải là N (const N &) –

+6

@Captain: Không thực sự. Cả hai đều hợp lệ. Tôi thích hình thức tôi sử dụng ở trên. –

Trả lời

15

Từ http://gcc.gnu.org/gcc-3.4/changes.html

When binding an rvalue of class type to a reference, the copy constructor of the class must be accessible. For instance, consider the following code:

class A 
{ 
public: 
    A(); 

private: 
    A(const A&); // private copy ctor 
}; 

A makeA(void); 
void foo(const A&); 

void bar(void) 
{ 
    foo(A());  // error, copy ctor is not accessible 
    foo(makeA()); // error, copy ctor is not accessible 

    A a1; 
    foo(a1);  // OK, a1 is a lvalue 
} 

This might be surprising at first sight, especially since most popular compilers do not correctly implement this rule (further details).

Điều này sẽ được khắc phục trong C++ 1x bởi Core Issue 391.

+0

Tôi có thể thấy sự cần thiết của một nhà xây dựng bản sao cho câu lệnh 'foo (makeA());' vì nó là kết quả của việc sao chép kết quả ngược lại từ hàm makeA() (ngay cả khi trình biên dịch loại bỏ bản sao thực tế thì nó sẽ ở đó). Nhưng câu lệnh 'foo (A())' không nên yêu cầu một bản sao (ngoài tiêu chuẩn yêu cầu nó) mà tôi hy vọng Vấn đề 391 được thiết kế để giải quyết. –

5

Các phần áp dụng của tiêu chuẩn ở đây là §8.5.3/5, bao gồm việc khởi tạo tham chiếu và §3.10/6, cho biết giá trị và giá trị (không phải lúc nào cũng rõ ràng trong C++).

Trong trường hợp này, biểu thức khởi tạo của bạn là: "N (1)", vì vậy bạn đang tạo một đối tượng rõ ràng bằng ký pháp chức năng. Theo 3.10/6, biểu thức đó là một giá trị.

Sau đó, chúng tôi phải đi qua các quy tắc trong 8.5.3/5 theo thứ tự và sử dụng quy tắc đầu tiên áp dụng. Khả năng đầu tiên là nếu biểu thức đại diện cho một lvalue, hoặc có thể được chuyển đổi hoàn toàn thành một lvalue. Biểu thức của bạn là một rvalue, và chuyển đổi ngầm thành một giá trị sẽ đòi hỏi một hàm chuyển đổi trả về một tham chiếu, mà dường như không tồn tại trong trường hợp này, do đó dường như không áp dụng.

Quy tắc tiếp theo cho biết tham chiếu phải là một const T (đây là trường hợp ở đây). Trong trường hợp này, biểu thức là một rvalue của loại lớp và tương thích với tham chiếu với tham chiếu (nghĩa là tham chiếu đến cùng một lớp hoặc một cơ sở của lớp). Điều đó có nghĩa là dấu đầu dòng ở cuối trang 151 (179 của tệp PDF C++ 2003) dường như được áp dụng. Trong trường hợp này, trình biên dịch được phép liên kết trực tiếp tham chiếu đến đối tượng đại diện cho giá trị rvalue, HOẶC tạo bản sao tạm thời của rvalue và liên kết với bản sao tạm thời đó.

Dù bằng cách nào, tiêu chuẩn yêu cầu rõ ràng rằng: "Nhà xây dựng sẽ được sử dụng để sao chép sẽ được gọi cho dù bản sao có thực sự được thực hiện hay không.. "

Như vậy, tôi tin rằng gcc là quyền đưa ra một thông báo lỗi, và những người khác về mặt kỹ thuật sai lầm khi chấp nhận mã tôi đơn giản hóa mã của bạn một chút như sau:

class N { 
    public: 
     N(int) {} 
    private: 
     N(N const&); 
}; 

void plop(N const& data) { } 

int main() { 
    plop(N(1)); 
} 

khi gọi với "--Địa" (chế độ lỗi nghiêm ngặt), Comeau cung cấp cho các thông báo lỗi sau:

"plop.cpp", line 12: error: "N::N(const N &)", required for copy that was 
      eliminated, is inaccessible 
     plop(N(1)); 
     ^

Tương tự như vậy, khi gọi với "/ Za" (nó "ANSI phù hợp" mode), VC++ 9 cung cấp cho:

plop.cpp 
plop.cpp(12) : error C2248: 'N::N' : cannot access private member declared in class 'N' 
     plop.cpp(6) : see declaration of 'N::N' 
     plop.cpp(2) : see declaration of 'N' 
     while checking that elided copy-constructor 'N::N(const N &)' is callable 
     plop.cpp(6) : see declaration of 'N::N' 
     when converting from 'N' to 'const N &' 

Tôi đoán là hầu hết các trình biên dịch khác đều gần giống nhau. Vì chúng tối ưu hóa cuộc gọi đến hàm tạo bản sao, chúng thường không yêu cầu nó tồn tại hoặc có thể truy cập được. Khi bạn yêu cầu họ tuân thủ các tiêu chuẩn chính xác nhất có thể, họ đưa ra thông báo lỗi, bởi vì nó được yêu cầu về mặt kỹ thuật mặc dù họ không sử dụng nó.

+0

Có thực sự là một bản sao được làm ở đây 'plop (N (1));'? Nếu có tôi không nhìn thấy nó như trình biên dịch được phép ràng buộc các tài liệu tham khảo trực tiếp đến đối tượng đại diện cho rvalue. Do đó, phần OR của cuộc hội thoại là không cần thiết và do đó không bắt buộc phải sao chép. Thật không may nó là đoạn tiếp theo (mà buộc một trình biên dịch phù hợp để sản xuất một lỗi) mà đau tôi. –

+0

@Martin: Tôi khó có thể tưởng tượng một trình biên dịch thực sự tạo ra một bản sao, thậm chí với mức tối ưu tối thiểu của nó (mặc dù tất nhiên nó * là * hầu như không thể). Lớp lót bạc là hầu hết các trình biên dịch (nghĩa là hợp lý) thường không thực thi nó, và với C++ 0x, yêu cầu sẽ biến mất. –