2012-01-06 8 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); } 
    }; 

Has ostrzeżenie kompilacjiCo oznacza komunikat "ma metodę wirtualną ... ale nie jest wirtualizatorem" podczas kompilacji C++?

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

Jak rozumieć to ostrzeżenie i jak poprawić kod?

[EDIT] czy ta wersja jest teraz poprawna? (Próbując odpowiedzieć, aby wyjaśnić sobie koncepcję)

#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

Tak, nowa wersja jest poprawna. Chociaż uważane jest za dobrą formę do ponownego deklarowania funkcji w klasach pochodnych jako wirtualnych, nawet jeśli nie jest to konieczne. Jest tak, że ludzie, którzy chcą tylko przyjrzeć się klasie pochodnej, nadal wiedzą, że funkcje są wirtualne. – Omnifarious

+0

Masz na myśli 'class CRectangle: public CPolygon { public: virtual int area() {return (szerokość * wysokość); } }; '? – qazwsx

+0

Tak. Oraz 'virtual ~ CRectangle() {}'. Jak już wspomniałem, przekonanie, że te funkcje są wirtualne, jest po prostu dobrą formą, w żaden sposób nie jest wymagane przez język. – Omnifarious

Odpowiedz

64

Jeśli klasa ma metodę wirtualną, oznacza to, że chcesz, aby inne klasy dziedziczyły po niej. Te klasy mogłyby zostać zniszczone przez referencję lub wskaźnik klasy bazowej, ale działałoby to tylko wtedy, gdyby klasa podstawowa posiadała wirtualny destruktor. Jeśli masz klasę, która powinna być użyta polimorficznie, powinna być również usuwana polimorficznie.

Na to pytanie odpowiada również głębia here. Poniżej znajduje się pełna przykład program, który pokazuje wpływ:

#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 
} 

wyjściowa:

Destructor of FooBase 
Destructor of Bar 
Destructor of BarBase 

Zauważ, że delete bar; przyczyny oba destruktory, ~Bar i ~BarBase, aby nazwać, podczas delete foo; wywołuje tylko ~FooBase. Ten ostatni jest nawet undefined behavior, więc efekt nie jest gwarantowany.

+0

Ta odpowiedź zostałaby znacznie poprawiona dzięki przykładowi pokazującemu złe efekty krojenia. Otrzyma ode mnie wiadomość, kiedy ją otrzyma. – Omnifarious

+1

@Omnifarious: Dodałem przykład. –

+0

Po prostu, aby było jasne: 'delete foo' wywołuje niezdefiniowane zachowanie, nie jest gwarantowane uruchamianie tylko' ~ FooBase'. – Mankarse

12

oznacza to, że potrzebujesz wirtualnego destruktora w klasie bazowej przy użyciu metod wirtualnych.

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

Pozostawienie go jest może doprowadzić do nieokreślonego zachowania, zazwyczaj pojawia się jako wyciek pamięci w narzędzia, takie jak valgrind.

+0

Jest to tylko UB, jeśli usuwany jest obiekt podklasy przez odniesienie lub wskaźnik klasy bazowej. –

+3

Pozostawienie tego nie jest - samo w sobie - niezdefiniowanym zachowaniem. Jedynym niezdefiniowanym zachowaniem, które może spowodować, jest to, że obiekt przydzielany dynamicznie typu pochodnego jest dealokowany za pomocą wyrażenia "delete", w którym typ operandu jest wskaźnikiem do klasy bazowej, gdzie klasa bazowa nie ma wirtualnego destruktora. Istnieją inne opcje zachęcające do bezpiecznego użytkowania, takie jak nadanie klasie chronionego nie-wirtualnego destruktora. –

+0

Z tego, co słyszałem, to głównie prowadzi do problemów, jeśli ty (lub ktoś) zdecyduje się przedłużyć tę strukturę. –

1

Oznacza to jedynie, że kod jak

CPolygon* p = new CRectangle; 
delete p; 

... lub cokolwiek zawijania do jakiegokolwiek inteligentnego wskaźnika, zasadniczo nie będzie zachowywać się poprawnie, ponieważ CPolygon nie jest polimorficzny o usunięcie, a część CRectange nie będzie właściwie zniszczone.

Jeśli nie usuniesz polimorficzności CRectangle i CPolygon, to ostrzeżenie nie ma znaczenia.

+2

Ale jeśli nie usuniesz polimorficzności CRectangle i CPolygon, destruktor klasy podstawowej powinien być zabezpieczony, aby wymusić to podczas kompilacji. –

+0

@MarkB: Nie koniecznie: jeśli CPolygon nie jest abstrakcyjny (nie wiem abstrakcji OP, jak głęboka jest), zarówno CRect, jak i CPolygon mogą być prawdziwymi doskonałymi prawnymi obywatelami stosu, uczestnicząc w algorytmach CPolygon poprzez referencje. Trzeba policzyć obszar, ale nie potrzeba polimorficznego niszczenia. A CPolygon jest zobowiązany sam ulec zniszczeniu (brak chronionego destruktora). Wyprowadzenie klasy, która nie ma wirtualnego destruktora, nie różni się od wyprowadzenia klasy, która nie ma WSZYSTKICH metod wirtualnych. Po prostu nie oczekuj wirtualnego zachowania wirtualnego. –

+0

@EmilioGaravaglia: Zazwyczaj zaleca się unikanie wyprowadzania z konkretnej klasy. Jest zbyt otwarty na niezamierzone krojenie w różnych postaciach. –

Powiązane problemy