2013-06-20 30 views
49

Trong Stack Overflow bài Checking the object type in C++11, tôi có nhận xét:Tại sao chúng ta cần sử dụng virtual ~ A() = mặc định; thay vì ảo ~ A() {} trong C++ 11?

Trong C++ 11 bạn thực sự sẽ muốn làm virtual ~A() = default; Nếu không, bạn sẽ mất các nhà thầu implict di chuyển.

virtual ~A() = default; là gì? Làm thế nào đến các nhà thầu di chuyển tiềm ẩn bị mất với virtual ~A() {}?

+13

+1 vì bạn đã tạo ra câu trả lời sai từ 10 nghìn người dùng. –

+6

Lưu ý rằng bạn luôn có thể nhận được hàm khởi tạo di chuyển bằng cùng một cơ chế: 'A (A &&) = mặc định; ' – Xeo

Trả lời

42

Nhận xét không chính xác.

Cả:

virtual ~A() = default; 

virtual ~A() {} 

là dùng tuyên bố. Và các thành viên di chuyển tiềm ẩn bị ức chế nếu destructor là người dùng khai báo.

[dcl.fct.def.default]/p4 thảo luận thành viên đặc biệt dùng cung cấp sử dụng tuyên bố:

Một hàm thành viên đặc biệt là dùng cung cấp nếu nó là người sử dụng -declared và không được mặc định hoặc xóa một cách rõ ràng trên lần khai báo đầu tiên.

+1

@HowardHinnant: Vậy trong cả hai trường hợp trình biên dịch sẽ không cung cấp một hàm tạo di chuyển? – legends2k

+2

@ legends2k: Đúng vậy. –

+0

@Pixelchemist: Tôi nghĩ bạn nói đúng. Mục 8.4.2 'Các hàm mặc định rõ ràng và các hàm được khai báo ngầm được gọi chung là các hàm mặc định, và việc triển khai sẽ cung cấp các định nghĩa ngầm định cho chúng. Một chức năng thành viên đặc biệt được người dùng cung cấp nếu nó được người dùng khai báo và không rõ ràng là được mặc định hoặc bị xóa trên khai báo đầu tiên của nó.' Đây là từ n3337 tức là C++ 11 standard + 1 – legends2k

5

Nhận xét đó sai. Thay vì cung cấp phương thức khởi động di chuyển của riêng bạn, nếu bạn muốn trình biên dịch cung cấp một, một trong các yêu cầu là nó hy vọng rằng destructor cũng được cung cấp bởi nó, tức là một destructor tầm thường. Tuy nhiên, tiêu chuẩn hiện tại là khá nghiêm ngặt khi thực hiện tiềm ẩn có thể được cung cấp — khi chấp nhận cách người dùng cấp quyền hủy. Bất cứ điều gì tuyên bố bởi người sử dụng được coi mà người dùng đang dùng vấn đề vào tay của mình và do đó không chỉ này

~A() { … } 

mà còn này

~A() = default; 

làm cho trình biên dịch không cung cấp một destructor ngầm. Đầu tiên là một định nghĩa và do đó một tuyên bố quá; thứ hai chỉ là một tuyên bố. Trong cả hai trường hợp, destructor là do người dùng khai báo và do đó ngăn cản trình biên dịch cung cấp một hàm tạo di chuyển ngầm.

Tôi đoán lý do đằng sau yêu cầu là khi di chuyển tài nguyên của đối tượng sang đối tượng khác để nguyên đối tượng gốc ở trạng thái không có tài nguyên trong bộ nhớ động; nhưng nếu lớp của bạn không có bất kỳ tài nguyên nào như vậy thì nó có thể được di chuyển, bị phá hủy, v.v. Khi bạn khai báo một destructor không tầm thường thì đó là một gợi ý cho trình biên dịch mà tài nguyên bạn quản lý trong lớp không phải là điều nhỏ nhặt và bạn hầu như cũng phải cung cấp số di chuyển không tầm thường, do đó trình biên dịch không cung cấp.

+4

+1 để nhận xét về lý do đằng sau hành vi này. –

+1

Ngoại trừ việc nó không phải là lý do đằng sau hành vi này, bởi vì đó là _not_ hành vi. –

+0

Đã sửa lỗi ngay bây giờ để giải quyết câu hỏi OP. – legends2k

27

Trong bài này https://stackoverflow.com/a/17204598/260127, tôi có nhận xét:

Trong C++ 11 bạn thực sự sẽ muốn làm virtual ~A() = default; Nếu không, bạn sẽ mất các nhà thầu implict di chuyển.

Nhận xét không đúng.

Thậm chí default ed, destructor đó là "sử dụng tuyên bố" (mặc dù lưu ý rằng nó không phải là cũng "dùng cung cấp").

#include <iostream> 

struct Helper 
{ 
    Helper() {} 
    Helper(const Helper& src) { std::cout << "copy\n"; } 
    Helper(Helper&& src)  { std::cout << "move\n"; } 
}; 

struct A 
{ 
    virtual ~A() {} 
    Helper h; 
}; 

struct B 
{ 
    virtual ~B() = default; 
    Helper h; 
}; 

struct C 
{ 
    Helper h; 
}; 


int main() 
{ 
    { 
     A x; 
     A y(std::move(x)); // outputs "copy", because no move possible 
    } 

    { 
     B x; 
     B y(std::move(x)); // outputs "copy", because still no move possible 
    } 

    { 
     C x; 
     C y(std::move(x)); // outputs "move", because no user-declared dtor 
    } 
} 

Live demo:

+ g ++ - 4,8 -std = C++ 11 -O2 Wall main.cpp -pthread
+ ./a.out
bản sao
bản sao
di chuyển

Vì vậy, bạn chưa "mất" bất cứ điều gì — không có mo đã có chức năng ở đó để bắt đầu!

Dưới đây là những đoạn văn tiêu chuẩn mà cấm một constructor chuyển ngầm trong cả trường hợp:

[C++11: 12.8/9]: 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 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 có một toán tử gán di chuyển do người dùng khai báo,
  • X không có một destructor sử dụng tuyên bố
  • các nhà xây dựng di chuyển sẽ không được định nghĩa rõ ràng là đã bị xóa.

Bootnote

Nó sẽ không làm tổn thương nếu một phiên bản tương lai của tiêu chuẩn thực sự liệt kê những ý nghĩa chính xác của thuật ngữ như "sử dụng tuyên bố". Có nghĩa là, ít nhất, điều này:

[C++11: 8.4.2/4]:[..] Một hàm thành viên đặc biệt là dùng cung cấp nếu nó là do người dùng khai báo và không rõ ràng mặc định hoặc xóa trên tuyên bố đầu tiên của mình. [..]

Người ta có thể giả định sự khác biệt ở đây bằng hàm ý.

+1

Mặc dù tôi tự hỏi tại sao tiêu chuẩn không cho phép thực hiện cung cấp một hàm tạo di chuyển khi không có _provided_ bởi người dùng nghĩa là để nó không khai báo và làm '= mặc định;' có vẻ giống nhau. – legends2k

+1

@ legends2k: Tôi đồng ý; lý tưởng '12.8/9' có thể nói" do người dùng cung cấp "thay thế. Có lẽ là '12.8/20', phải không? –

+0

@ legends2k: Và, vâng, mọi người đều thích một testcase tốt. : D –

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