Почему (виртуальный) деструктор базового класса называют, когда объект производного класса удален?

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

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

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

32
задан Georg Fritzsche 16 July 2010 в 03:35
поделиться

7 ответов

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

После выполнения тела деструктора и уничтожения любых автоматических объектов, размещенных в теле, деструктор для класса X вызывает деструкторы для прямых невариантных членов X, деструкторы для прямых базовые классы и, , если X является типом наиболее производного класса (12.6.2), его деструктор вызывает деструкторы для Виртуальные базовые классы X . Все деструкторы вызываются так, как если бы на них ссылались с определенным именем, то есть игнорирование любых возможных виртуальных переопределяющих деструкторов в более производных классах. Базы и члены уничтожены в порядке, обратном завершению их конструктора (см. 12.6.2). Оператор возврата (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).
 }
16
ответ дан 27 November 2019 в 20:59
поделиться

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

Конструктор

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

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

вывод:

~E
~D
~C

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

13
ответ дан 27 November 2019 в 20:59
поделиться

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

2
ответ дан 27 November 2019 в 20:59
поделиться

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

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

struct B : A {};

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

2
ответ дан 27 November 2019 в 20:59
поделиться

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

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

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

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

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

2
ответ дан 27 November 2019 в 20:59
поделиться

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

0
ответ дан 27 November 2019 в 20:59
поделиться

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

Из C++ spec:

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

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

11
ответ дан 27 November 2019 в 20:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: