Почему деструктор базового класса (виртуальный) вызывается при удалении объекта производного класса? - PullRequest
31 голосов
/ 16 июля 2010

Разница между деструктором (конечно, также конструктором) и другими функциями-членами заключается в том, что, если обычная функция-член имеет тело в производном классе, выполняется только версия в производном классе. Тогда как в случае деструкторов выполняются как производные, так и версии базового класса?

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

Заранее спасибо!

Ответы [ 7 ]

16 голосов
/ 16 июля 2010

Стандарт гласит:

После выполнения тела деструктора и уничтожения любых автоматических объектов, размещенных внутри тела, деструктор для класса X вызывает деструкторы для прямых неизменяемых членов X, деструкторы для прямых базовых классов X и , если X является типом самого производного класса (12.6.2), его деструктор вызывает деструкторы для виртуальных базовых классов X .Все деструкторы вызываются так, как будто на них ссылается квалифицированное имя, то есть игнорируются любые возможные виртуальные переопределители в более производных классах. Базы и элементы уничтожаются в порядке, обратном завершению их конструктора (см. 12.6.2).Оператор return (6.6.3) в деструкторе может не возвращаться напрямую к вызывающей стороне;перед передачей управления вызывающей стороне деструкторы для членов и баз вызываются.Деструкторы для элементов массива вызываются в обратном порядке их построения (см. 12.6).

Также согласно RAII ресурсы должны быть привязаны к продолжительности жизни подходящих объектов идеструкторы соответствующих классов должны вызываться для освобождения ресурсов.

Например, следующий код пропускает память.

 struct Base
 {
       int *p;
        Base():p(new int){}
       ~Base(){ delete p; } //has to be virtual
 };

 struct Derived :Base
 {
       int *d;
       Derived():Base(),d(new int){}
       ~Derived(){delete d;}
 };

 int main()
 {
     Base *base=new Derived();
     //do something

     delete base;   //Oops!! ~Base() gets called(=>Memory Leak).
 }
13 голосов
/ 16 июля 2010

Конструктор и деструктор отличаются от остальных обычных методов.

Конструктор

  • не может быть виртуальным
  • в производном классе вы либо явно вызываете конструктор базового класса
  • , либо, если вы не вызываете конструктор базового класса, компилятор вставит вызов.Он вызовет базовый конструктор без параметров.Если такого конструктора не существует, вы получите ошибку компилятора.

struct A {};
struct B : A { B() : A() {} };

// but this works as well because compiler inserts call to A():
struct B : A { B() {} };

// however this does not compile:
struct A { A(int x) {} };
struct B : A { B() {} };

// you need:
struct B : A { B() : A(4) {} };

Деструктор :

  • при вызове деструктора в производном классеповерх указателя или ссылки, где базовый класс имеет виртуальный деструктор, сначала будет вызван самый производный деструктор, а затем остальные производные классы в обратном порядке построения.Это делается для того, чтобы убедиться, что вся память была должным образом очищена.Это не сработало бы, если самый производный класс был вызван последним, потому что к тому времени базовый класс уже не существовал в памяти, и вы бы получили segfault.

struct C
{
    virtual ~C() { cout << __FUNCTION__ << endl; }
};

struct D : C
{
    virtual ~D() { cout << __FUNCTION__ << endl; }
};

struct E : D
{
    virtual ~E() { cout << __FUNCTION__ << endl; }
};

int main()
{
    C * o = new E();
    delete o;
}

output:

~E
~D
~C

Если метод в базовом классе помечен как virtual, то все унаследованные методы также являются виртуальными, поэтому даже если вы не пометите деструкторы в D и E как virtual, они будутвсе еще быть virtual, и они все еще вызывают в том же порядке.

11 голосов
/ 16 июля 2010

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

Из C ++ spec :

После выполнения тела деструктора и уничтожения любых автоматических объектов, размещенных в теле, деструктор для класса X вызывает деструкторы для прямых членов X, деструкторы для прямых базовых классов X и, если X является типом самого производного класса (12.6.2), его деструктор вызывает деструкторы для виртуальных базовых классов X.Все деструкторы вызываются так, как если бы на них ссылались с определенным именем, то есть игнорируя любые возможные виртуальные перезаписывающие деструкторы в более производных классах.Базы и члены уничтожаются в порядке, обратном завершению их конструктора (см. 12.6.2).

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

2 голосов
/ 16 июля 2010

Деструктор базового класса может отвечать за очистку ресурсов, выделенных конструктором базового класса.

Если ваш базовый класс имеет конструктор по умолчанию (тот, который не принимает параметры или имеет значения по умолчанию для всех его параметров), этот конструктор автоматически вызывается при построении производного экземпляра.

Если ваш базовый класс имеет конструктор, который требует параметров, вы должны вызвать его вручную в списке инициализатора конструктора производного класса.

Ваш деструктор базового класса всегда будет вызываться автоматически при удалении производного экземпляра, поскольку деструкторы не принимают параметры.

Если вы используете полиморфизм и на ваш производный экземпляр указывает указатель базового класса, то деструктор производного класса вызывается только в том случае, если базовый деструктор является виртуальным.

2 голосов
/ 16 июля 2010

Как говорит Игорь, конструкторы должны вызываться для базовых классов. Посмотрим, что произойдет, если он не будет назван:

struct A {
    std::string s;
    virtual ~A() {}
};

struct B : A {};

Если деструктор для A не будет вызван при удалении экземпляра B, A никогда не будет очищен.

2 голосов
/ 16 июля 2010

Потому что так работает дтор.Когда вы создаете объект, ctors вызываются начиная с базы и заканчивая самыми производными.Когда вы уничтожаете объекты (правильно), происходит обратное.Время, когда виртуализация dtor имеет значение, - это если / когда вы уничтожаете объект с помощью указателя (или ссылки, хотя это довольно необычно) на базовый тип.В этом случае альтернатива на самом деле не в том, что вызывается только производный dtor - скорее, альтернатива - просто неопределенное поведение.Это может принять форму вызова только производного dtor, но это также может принять совершенно иную форму.

0 голосов
/ 16 июля 2010

Когда любой объект уничтожается, деструкторы запускаются для всех подобъектов.Это включает в себя как повторное использование по содержанию, так и повторное использование по наследству.

...