Câu trả lời:
Nó thậm chí còn quan trọng hơn đối với một giao diện. Bất kỳ người dùng nào trong lớp của bạn có thể sẽ giữ một con trỏ tới giao diện, không phải là một con trỏ để triển khai cụ thể. Khi họ đến để xóa nó, nếu hàm hủy không phải là ảo, họ sẽ gọi hàm hủy của giao diện (hoặc mặc định do trình biên dịch cung cấp, nếu bạn không chỉ định một), không phải là hàm hủy của lớp dẫn xuất. Rò rỉ bộ nhớ tức thì.
Ví dụ
class Interface
{
virtual void doSomething() = 0;
};
class Derived : public Interface
{
Derived();
~Derived()
{
// Do some important cleanup...
}
};
void myFunc(void)
{
Interface* p = new Derived();
// The behaviour of the next line is undefined. It probably
// calls Interface::~Interface, not Derived::~Derived
delete p;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Nó vẫn sẽ không được xác định nếu Derogen sử dụng một hàm hủy được tạo ngầm.
Câu trả lời cho câu hỏi của bạn là thường xuyên, nhưng không phải lúc nào cũng vậy. Nếu lớp trừu tượng của bạn cấm khách hàng gọi xóa trên một con trỏ tới nó (hoặc nếu nó nói như vậy trong tài liệu của nó), bạn có quyền không khai báo một hàm hủy ảo.
Bạn có thể cấm khách hàng gọi xóa trên một con trỏ tới nó bằng cách làm cho hàm hủy của nó được bảo vệ. Làm việc như thế này, hoàn toàn an toàn và hợp lý khi bỏ qua một hàm hủy ảo.
Cuối cùng, bạn sẽ không có bảng phương thức ảo và cuối cùng báo hiệu cho khách hàng của bạn ý định làm cho nó không bị xóa thông qua một con trỏ tới nó, vì vậy bạn thực sự có lý do để không khai báo ảo trong những trường hợp đó.
[Xem mục 4 trong bài viết này: http://www.gotw.ca/publications/mill18.htm ]
Tôi quyết định thực hiện một số nghiên cứu và cố gắng tóm tắt câu trả lời của bạn. Các câu hỏi sau đây sẽ giúp bạn quyết định loại hủy nào bạn cần:
Tôi hi vọng cái này giúp được.
* Điều quan trọng cần lưu ý là không có cách nào trong C ++ để đánh dấu một lớp là cuối cùng (nghĩa là không thể phân lớp), vì vậy trong trường hợp bạn quyết định tuyên bố kẻ hủy diệt của mình không ảo và công khai, hãy nhớ cảnh báo rõ ràng với các lập trình viên của bạn chống lại xuất phát từ lớp học của bạn.
Người giới thiệu:
Vâng, nó luôn luôn quan trọng. Các lớp dẫn xuất có thể phân bổ bộ nhớ hoặc giữ tham chiếu đến các tài nguyên khác sẽ cần được dọn sạch khi đối tượng bị phá hủy. Nếu bạn không cung cấp các hàm hủy ảo giao diện / lớp trừu tượng, thì mỗi khi bạn xóa một thể hiện của lớp dẫn xuất thông qua một lớp cơ sở thì hàm hủy của lớp dẫn xuất của bạn sẽ không được gọi.
Do đó, bạn đang mở ra khả năng rò rỉ bộ nhớ
class IFoo
{
public:
virtual void DoFoo() = 0;
};
class Bar : public IFoo
{
char* dooby = NULL;
public:
virtual void DoFoo() { dooby = new char[10]; }
void ~Bar() { delete [] dooby; }
};
IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
Nó không phải luôn luôn được yêu cầu, nhưng tôi thấy đó là một thực hành tốt. Những gì nó làm, là nó cho phép một đối tượng dẫn xuất được xóa an toàn thông qua một con trỏ của một loại cơ sở.
Ví dụ:
Base *p = new Derived;
// use p as you see fit
delete p;
không được định dạng nếu Base
không có hàm hủy ảo, vì nó sẽ cố xóa đối tượng như thể nó là a Base *
.
shared_ptr
này sẽ cố gắng xóa đối tượng như thể nó là một Base *
- nó nhớ loại vật mà bạn đã tạo ra nó. Xem liên kết được tham chiếu, cụ thể là bit có nội dung "Hàm hủy sẽ gọi xóa với cùng một con trỏ, hoàn thành với loại ban đầu của nó, ngay cả khi T không có hàm hủy ảo hoặc bị hủy."
Đó không chỉ là thực hành tốt. Đó là quy tắc số 1 cho bất kỳ hệ thống phân cấp lớp nào.
Bây giờ cho Tại sao. Lấy thứ bậc động vật điển hình. Các hàm hủy ảo đi qua công văn ảo giống như bất kỳ lệnh gọi phương thức nào khác. Lấy ví dụ sau.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Giả sử rằng Animal là một lớp trừu tượng. Cách duy nhất mà C ++ biết hàm hủy thích hợp để gọi là thông qua phương thức ảo. Nếu hàm hủy không phải là ảo thì nó sẽ chỉ gọi hàm hủy của Animal và không hủy bất kỳ đối tượng nào trong các lớp dẫn xuất.
Lý do làm cho hàm hủy ảo trong lớp cơ sở là vì nó đơn giản loại bỏ sự lựa chọn khỏi các lớp dẫn xuất. Kẻ hủy diệt của chúng trở thành ảo theo mặc định.
Câu trả lời rất đơn giản, bạn cần nó là ảo nếu không lớp cơ sở sẽ không phải là một lớp đa hình hoàn chỉnh.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Bạn muốn xóa phần trên, nhưng nếu hàm hủy của lớp cơ sở không phải là ảo, chỉ có hàm hủy của lớp cơ sở sẽ được gọi và tất cả dữ liệu trong lớp dẫn xuất sẽ không bị xóa.
delete p
gọi hành vi không xác định. Nó không được đảm bảo để gọiInterface::~Interface
.