2012-06-25 2 views
2

Зачем нужна следующая программа? У меня есть базовый класс, деструктор не является виртуальным, но дочерний класс деструктор виртуальный:Деструктор базового класса не виртуальный, а деструктор дочернего класса виртуальный, авария программы

#include <iostream> 

class Base { 
public: 
    Base() { 
    std::cout << "Base::Base CTOR " << std::endl; 
    } 
    ~Base() { 
    std::cout << "Base::Base DTOR " << std::endl; 
    } 
}; 

class Child : public Base { 
public: 
    Child(){ 
    std::cout << "Child::Child CTOR " << std::endl; 
    } 
    virtual ~Child() { 
    std::cout << "Child::Child DTOR " << std::endl; 
    } 

}; 

int main (int argc, char **argv) { 
    Base *ptr = new Child; 
    delete ptr; 
} 
+3

Деструктор базового класса должен быть «виртуальным» – Mohammad

+0

, вы должны сделать виртуальный виртуальный демер вашего базового класса, см. Эту ссылку для получения более подробной информации http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq -20.7 – Sanish

+0

он сработал * после * или написал «Base :: Base DTOR» или он никогда не записывал это? – Walter

ответ

7

То, что вы наблюдаете, называется «неопределенное поведение». Сделайте Base dtor виртуальный, если вы хотите сделать вызов delete на Child экземпляр через Base указатель.

От стандарта 2003, 5.3.5/3:

В первом варианте (удалить объект), если статический тип операнда отличается от своего динамического типа, статического типа, должен быть a базовый класс динамического типа операнда и статический тип должен иметь , имеет виртуальный деструктор или поведение не определено.

+0

Можете ли вы сказать, почему это UB? – unkulunkulu

+0

@unkulunkulu: Потому что он лжет компилятору о памяти, которую хочет освободить. – wilx

+0

освобожденная память не зависит от вызываемого непосредственно деструктора (ну, может быть, если есть некоторые ресурсы памяти, хранящиеся в 'Child'). Фактически, я искал точное утверждение, что составляет это UB, в идеале это будет ссылка на стандарт. – unkulunkulu

5

У вас есть неопределенное поведение, потому что статический тип операнда-указателя не совпадает с динамическим типом объекта, на который он указывает, и вы не отвечаете требованиям к исключению из этого правила, которое позволяет проходить указатель на базовый класс на удаляемый объект, потому что для этого исключения требуется класс , чтобы иметь виртуальный деструктор.

Любое поведение возможно, включая код, работающий «как ожидалось» или сбой.

+0

Вы можете немного перефразировать, потому что слово «исключение» меня смутило (я думал о 'C++' исключениях). Собственно, я понял ваш ответ только после прочтения цитаты @ wilx со стандарта. – unkulunkulu

+0

@unkulunkulu: Я рассматривал перефразирование, но прямо сейчас я не могу представить лучшего слова, чем «исключение», чтобы описать исключение из правила. –

+0

Но вы можете перефразировать не только путем замены этой работы, вы можете добавить еще одно предложение, например: «вы не можете этого сделать, но есть исключение, но в вашем случае вы не отвечаете требованиям» или что-то еще: D – unkulunkulu

1

Надежда этот пример поможет вам получить точку:

#include <iostream> 
class Base { 
public: 
Base() { 
    std::cout << "Base::Base CTOR " << std::endl; 
} 
~Base() { 
    std::cout << "Base::Base DTOR " << std::endl; 
} 
private: 
protected: 
}; 

class Child : public Base { 
public: 
Child(){ 
std::cout << "Child::Child CTOR " << std::endl; 
    } 
    ~Child(){ 
std::cout << "Child::Child DTOR " << std::endl; 
} 
    private: 
protected: 
}; 
    class gChild : public Child { 
    public: 
    gChild(){ 
    std::cout << "Child::Child gCTOR " << std::endl; 
    } 
    ~gChild(){ 
    std::cout << "Child::Child gDTOR " << std::endl; 
    } 
private: 
protected: 
}; 
int main (int argc, char **argv) { 
    Base *ptr = new gChild; 
delete ptr; 
} 

если виртуальная ~ Base(), то печать всех деструкторов получает распечатанный.

Если виртуальный ~ ребенок() или виртуальный ~ gChild(), печатается только базовый деструктор.

Это потому, что деструкторы выполняются в противоположном направлении. И здесь поведение не определено. Вы должны определить базовый деструктор виртуальный, чтобы получить ожидаемый результат.

Спасибо.

1

Просто посмотрите на это:

#include <iostream> 

class Base 
{ 
public: 
    void nonvirtualmethod() 
    { std::cout << "Base nonvirtualmethod" << std::endl; } 
    virtual void virtualmethod() 
    { std::cout << "Base virtualmethod" << std::endl; } 
}; 

class Derived: public Base 
{ 
public: 
    void nonvirtualmethod() 
    { std::cout << "Derived nonvirtualmethod" << std::endl; } 
    virtual void virtualmethod() 
    { std::cout << "Derived virtualmethod" << std::endl; } 
}; 

int main() 
{ 
    Derived d; 
    Derived* pd = &d; 
    Base* pb = &d; //< NOTE: both pd and pb point to the same object 

    pd->nonvirtualmethod(); 
    pb->nonvirtualmethod(); 
    pd->virtualmethod(); 
    pb->virtualmethod(); 
} 

I дает следующий результат:

Derived nonvirtualmethod 
Base nonvirtualmethod 
Derived virtualmethod 
Derived virtualmethod //< invoked by a Base* 

Это происходит потому, что есть разница между статическим типом pb указателя (Base*) и динамический тип, на который он указывает (Derived). Разница между виртуальными и обычными методами заключается в том, что не виртуальные методы следуют за сопоставлением статического типа (поэтому указатель Base вызывает методы Base::), тогда как виртуальные методы следуют цепочке типов времени выполнения, следовательно, если Base* указывает на Derived, вызывается метод Derived.

деструкторы, в этом смысле, ничего особенного: если это не виртуальная, Base указатель не будет вызывать Derived один, следовательно, вы остались с полуразрушенного объекта, который возвращается в магазин памяти.

Причина, по которой это UB (а не просто отказ), заключается в том, что «хранилище памяти» не управляется самим языком, а с платформы, на которой размещена программа: авария, скорее всего, зависит от факта что отсутствующая часть Derived (все еще живая) приведет к тому, что операционная система попробует освободить блок памяти с неправильным стартовым адресом.