2012-01-06 23 views
29
#include <iostream> 
using namespace std; 

class CPolygon { 
    protected: 
    int width, height; 
    public: 
    virtual int area() 
     { return (0); } 
    }; 

class CRectangle: public CPolygon { 
    public: 
    int area() { return (width * height); } 
    }; 

Có cảnh báo biên soạnCảnh báo 'có phương pháp ảo ... nhưng cảnh báo hủy không ảo' có nghĩa là gì trong quá trình biên dịch C++?

Class '[[email protected]' has virtual method 'area' but non-virtual destructor 

Làm thế nào để hiểu được cảnh báo này và làm thế nào để cải thiện mã?

[EDIT] là phiên bản này đúng không? (Cố gắng trả lời để làm sáng tỏ bản thân mình với khái niệm này)

#include <iostream> 
using namespace std; 

class CPolygon { 
    protected: 
    int width, height; 
    public: 
    virtual ~CPolygon(){}; 
    virtual int area() 
     { return (0); } 
    }; 

class CRectangle: public CPolygon { 
    public: 
    int area() { return (width * height); } 
    ~CRectangle(){} 
    }; 
+0

Có, phiên bản mới là chính xác. Mặc dù nó được coi là hình thức tốt để tái tuyên bố các chức năng trong các lớp dẫn xuất như ảo mặc dù nó không cần thiết. Điều này là để những người chỉ muốn nhìn vào lớp dẫn xuất vẫn biết các chức năng là ảo. – Omnifarious

+0

Bạn có nghĩa là 'lớp CRectangle: public CPolygon { công khai: vùng int ảo() {return (width * height); } }; '? – qazwsx

+0

Có. Và 'virtual ~ CRectangle() {}' là tốt. Như tôi đã nói, hãy nhớ rằng các chức năng này là ảo chỉ đơn giản là hình thức tốt, nó không được yêu cầu bởi ngôn ngữ theo bất kỳ cách nào. – Omnifarious

Trả lời

64

Nếu lớp học có phương thức ảo, điều đó có nghĩa là bạn muốn các lớp khác kế thừa từ đó. Các lớp này có thể bị phá hủy thông qua một tham chiếu lớp cơ sở hoặc con trỏ, nhưng điều này sẽ chỉ hoạt động nếu lớp cơ sở có một destructor ảo. Nếu bạn có một lớp được cho là có thể sử dụng được đa hình, nó cũng sẽ có thể xóa được đa hình.

Câu hỏi này cũng được trả lời ở độ sâu here. Sau đây là một chương trình ví dụ hoàn chỉnh đó chứng tỏ tác dụng:

#include <iostream> 

class FooBase { 
public: 
    ~FooBase() { std::cout << "Destructor of FooBase" << std::endl; } 
}; 

class Foo : public FooBase { 
public: 
    ~Foo() { std::cout << "Destructor of Foo" << std::endl; } 
}; 

class BarBase { 
public: 
    virtual ~BarBase() { std::cout << "Destructor of BarBase" << std::endl; } 
}; 

class Bar : public BarBase { 
public: 
    ~Bar() { std::cout << "Destructor of Bar" << std::endl; } 
}; 

int main() { 
    FooBase * foo = new Foo; 
    delete foo; // deletes only FooBase-part of Foo-object; 

    BarBase * bar = new Bar; 
    delete bar; // deletes complete object 
} 

Output:

Destructor of FooBase 
Destructor of Bar 
Destructor of BarBase 

Lưu ý rằng delete bar; nguyên nhân cả destructors, ~Bar~BarBase, được gọi là, trong khi delete foo; chỉ gọi ~FooBase. Sau này thậm chí là undefined behavior, vì vậy hiệu quả đó không được đảm bảo.

+0

Câu trả lời này sẽ được cải thiện rất nhiều với một ví dụ minh họa các tác động xấu của việc cắt. Nó sẽ nhận được một upvote từ tôi khi nó có nó. – Omnifarious

+1

@Omnifarious: Tôi đã thêm một ví dụ. –

+0

Chỉ cần rõ ràng: 'delete foo' gọi hành vi không xác định, nó không được bảo đảm chỉ chạy' ~ FooBase'. – Mankarse

12

nó có nghĩa là bạn cần một trình phá hủy ảo trên lớp cơ sở với các phương pháp ảo.

struct Foo { 
    virtual ~Foo() {} 
    virtual void bar() = 0; 
}; 

Rời nó đi có thể dẫn đến hành vi không xác định, thường xuất hiện như một rò rỉ bộ nhớ trong các công cụ như valgrind.

+0

Nó chỉ là UB nếu bạn xóa một đối tượng phụ lớp thông qua một tham chiếu lớp cơ sở hoặc con trỏ. –

+3

Rời khỏi nó không phải là - trong và của chính nó - hành vi không xác định. Hành vi không xác định duy nhất mà nó có thể gây ra là nếu một đối tượng được cấp phát động của một kiểu dẫn xuất được deallocated với biểu thức 'delete' trong đó kiểu toán hạng là con trỏ tới lớp cơ sở trong đó lớp cơ sở không có một destructor ảo. Có các tùy chọn khác để khuyến khích việc sử dụng an toàn như đưa cho lớp một trình phá hủy không ảo được bảo vệ. –

+0

Từ những gì tôi đã nghe, nó chủ yếu dẫn đến các vấn đề nếu bạn (hoặc ai đó) quyết định mở rộng ra khỏi cấu trúc đó. –

1

Nó chỉ có nghĩa là một mã như

CPolygon* p = new CRectangle; 
delete p; 

... hoặc bất cứ gói vào bất cứ điều gì con trỏ thông minh, về cơ bản sẽ không cư xử một cách chính xác kể từ CPolygon không phải là đa hình về xóa, và phần CRectange sẽ không được bị phá hủy đúng cách.

Nếu bạn không xóa CRectangle và đa hình CPolygon, cảnh báo đó không có ý nghĩa.

+2

Nhưng nếu bạn không xóa CRectangle và CPolygon polymorphicaly, lớp hủy lớp cơ sở cần được bảo vệ để thực thi điều này tại thời gian biên dịch. –

+0

@MarkB: Không cần thiết: Nếu CPolygon không trừu tượng (tôi không biết OP trừu tượng sâu bao nhiêu), cả CRect và CPolygon có thể là công dân hợp pháp hoàn hảo của chồng, tham gia vào thuật toán CPolygon thông qua tài liệu tham khảo. Khu vực cần phải được tính toán đa hình, nhưng không cần phá hủy đa hình. Và CPolygon được yêu cầu phải tự hủy diệt (không có destructor được bảo vệ). Tạo ra một lớp không có trình phá hủy ảo không khác với việc tạo ra một lớp không có phương thức ALL ảo. Đơn giản chỉ cần không mong đợi những gì không phải là ảo để cư xử hầu như. –

+0

@EmilioGaravaglia: Thường được khuyên nên tránh bắt nguồn từ một lớp bê tông. Nó quá mở để cắt không chủ định trong nhiều loại vỏ bọc khác nhau. –

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