2016-05-17 18 views
10

Theo tiêu chuẩn,Mặc định di chuyển constructor/nhiệm vụ và xóa constructor sao chép/chuyển nhượng

Nếu định nghĩa của một lớp X không tuyên bố một cách rõ ràng một constructor di chuyển, người ta sẽ được khai báo ngầm như mặc định khi và chỉ nếu

- X không có một người dùng tuyên bố constructor sao chép,

- X không có một toán tử gán bản sao do người dùng khai báo,

- X không h ave một toán tử gán nhiệm vụ do người dùng khai báo và

- X không có trình phá hủy do người dùng khai báo.

Bây giờ sau thất bại trong việc biên dịch

# include <utility> 

class Foo 
{ 
public: 
    Foo() = default; 
    Foo(Foo const &) = delete; 
}; 

int main() 
{ 
    Foo f; 
    Foo g(std::move(f)); // compilation fails here 
    return 0; 
} 

Vì vậy, có vẻ như một chức năng xóa được coi là người dùng định nghĩa, có ý nghĩa (nó không phải là thực hiện mặc định của nó). Tuy nhiên, trong trường hợp cụ thể, làm thế nào sẽ bị xóa sao chép construtor/gán mess mặc định di chuyển constructor/chuyển nhượng?

Tôi nghĩ câu hỏi này có tầm quan trọng thực tế vì tạo thủ công và đặc biệt. việc duy trì các chức năng mặc định như vậy là dễ bị lỗi, trong khi cùng lúc, sự gia tăng (sự công bình) của việc sử dụng các lớp như std::unique_ptr khi các thành viên của lớp làm cho các lớp không thể sao chép được nhiều con thú thông thường hơn trước đây.

+0

Không chắc chắn để hiểu rõ câu hỏi của bạn nhưng tại sao bạn không mặc định ctor di chuyển? tức là 'Foo (Foo &&) = mặc định; ' –

Trả lời

12

user-declared có nghĩa là một trong hai dùng cung cấp (định nghĩa bởi người sử dụng), rõ ràng mặc định (= default) hoặc xóa một cách rõ ràng (= delete) trái ngược với ngầm mặc định/xóa (ví dụ như xây dựng di chuyển của bạn).

Vì vậy, trong trường hợp của bạn, các nhà xây dựng thái này là ngầm xóa vì sao chép constructor là rõ ràng xóa (và do đó sử dụng tuyên bố).

Tuy nhiên, trong trường hợp cụ thể đó, cách xóa trình tạo/gán nhiệm vụ mặc định của trình tạo bản sao?

Nó sẽ không, nhưng tiêu chuẩn không tạo sự khác biệt giữa trường hợp này và sự phức tạp.

Câu trả lời ngắn nhất là có một ngầm định nghĩa di chuyển-nhà xây dựng với một rõ ràng xóa sao chép constructor thể thể gây nguy hiểm trong một số trường hợp, tương tự khi bạn có một người dùng định nghĩa destructor và không do người dùng xác định trình tạo bản sao (xem rule of three/five/zero). Bây giờ, bạn có thể tranh luận rằng một destructor do người dùng định nghĩa không xóa bản sao-constructor, nhưng điều này chỉ đơn giản là một lỗ hổng bằng ngôn ngữ không thể xóa được vì nó sẽ phá vỡ rất nhiều chương trình cũ (xấu). Để báo giá Bjarne Stroustrup:

Trong một thế giới lý tưởng, tôi nghĩ chúng tôi sẽ quyết định "không tạo ra" làm mặc định và cung cấp một ký hiệu đơn giản cho "cung cấp cho tôi tất cả các hoạt động thông thường" [...] Ngoài ra, chính sách “không có hoạt động mặc định” dẫn đến biên dịch lỗi thời gian (chúng ta nên có cách dễ dàng để sửa), trong khi đó hoạt động tạo theo chính sách mặc định dẫn đến các vấn đề không thể phát hiện được cho đến thời gian chạy.

Bạn có thể đọc thêm về điều này trong N3174=10-0164.

Lưu ý rằng hầu hết mọi người theo dõi rule of three/five/zero và theo ý kiến ​​của tôi, bạn nên làm như vậy. Bằng cách xóa hoàn toàn hàm khởi tạo mặc định, tiêu chuẩn là "bảo vệ" bạn khỏi những sai lầm và nên bảo vệ bạn một thời gian dài trước khi xóa bản sao-constructor trong một số trường hợp (xem giấy của Bjarne).

đọc

Hơn nữa nếu bạn quan tâm:

Tôi nghĩ rằng câu hỏi này có tầm quan trọng thiết thực vì thủ công thế hệ và đặc biệt. việc duy trì các chức năng mặc định như vậy là dễ bị lỗi, trong khi cùng lúc, sự gia tăng (sự công bình) của việc sử dụng các lớp như std::unique_ptr khi các thành viên của lớp làm cho các lớp không thể sao chép được nhiều con thú thông thường hơn trước đây.

Đánh dấu các nhà xây dựng di chuyển như một cách rõ ràng mặc định sẽ giải quyết vấn đề này:

class Foo { 
public: 
    Foo() = default; 
    Foo(Foo const &) = delete; 
    Foo(Foo&&) = default; 
}; 

Bạn nhận được một đối tượng phi copyable với một constructor di chuyển mặc định, và theo ý kiến ​​của tôi những tờ khai rõ ràng là tốt hơn so với những người tiềm ẩn (ví dụ: chỉ khai báo hàm khởi tạo di chuyển là default mà không xóa trình tạo bản sao).

+0

@NathanOliver Có một số ở đây http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm. – Holt

+0

Cảm ơn. Tôi tìm thấy một số là tốt. Điều này với tôi trông giống như câu trả lời tốt nhất. – NathanOliver

-1

Các hành vi cơ bản có thể được minh họa bằng:

void foo(const int& i) 
{ 
    std::cout << "copy"; 
} 

void foo(int&& i) 
{ 
    std::cout << "move"; 
} 

Khi cả hai quá tải có mặt, sự quá tải int&& được chọn để rvalues ​​trong khi quá tải const int& được chọn để lvalues. Nếu bạn loại bỏ các quá tải int&&, const int& được gọi là (ergo, không phải là một lỗi) ngay cả đối với giá trị. Đây thực chất là những gì đang diễn ra trong trường hợp này:

class Foo 
{ 
public: 
    Foo() = default; 
    Foo(Foo const &) = delete; 
}; 

Độ phân giải quá tải chỉ thấy một ứng cử viên nhưng vì nó bị xóa một cách rõ ràng, chương trình bị hỏng.

Những lưu ý không quy chuẩn dưới đây phần bạn trích dẫn làm rõ đây là những gì đang xảy ra:

[Ghi chú: Khi các nhà xây dựng di chuyển không được mặc nhiên tuyên bố hoặc một cách rõ ràng cung cấp, biểu thức mà nếu không sẽ gọi các di chuyển constructor có thể thay vì gọi một constructor sao chép. - cuối note ]

+0

Tôi * hy vọng * đó là một sự phản kháng. –

1

Như bạn nói, từ §12.8

Nếu định nghĩa của một lớp X không tuyên bố một cách rõ ràng một constructor di chuyển, người ta sẽ được khai báo ngầm như mặc định khi và chỉ khi

  • X không có một sử dụng tuyên bố copy constructor,

  • [...]

Lưu ý do người dùng khai báo. Nhưng nếu bạn nhìn vào §8.4.3:

Một định nghĩa hàm có dạng:

thuộc tính-specifier-seq opt decl-specifier-seq opt declarator virt-specifier-seq chọn = xóa;

được gọi là xóa định nghĩa. Hàm có định nghĩa đã xóa cũng được gọi là hàm đã xóa.

Chương trình đề cập đến chức năng bị xóa hoàn toàn hoặc rõ ràng, khác với tuyên bố nó, bị lỗi.

Vì vậy, tiêu chuẩn định nghĩa delete d chức năng như sử dụng tuyên bố (như bạn có thể nhìn thấy từ trên cao), mặc dù họ là delete d và không thể được sử dụng.

Sau đó, theo §12.8, hàm tạo di chuyển ngầm không được xác định, vì người xây dựng bản sao được người dùng khai báo (với = delete;).

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