Подтвердить что ты не робот

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

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

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

Спасибо заранее!

4b9b3361

Ответ 1

В стандарте говорится

После выполнения тела деструктора и уничтожения любых автоматических объектов, выделенных в теле, деструктор класса X вызывает деструкторы для прямых невариантных элементов Xs, деструкторы для Xs direct базовые классы и , если X - тип самого производного класса (12.6.2), его деструктор вызывает деструкторы для Xs виртуальные базовые классы. Все деструкторы вызываются так, как если бы они ссылались на квалифицированное имя, игнорируя любые возможные виртуальные переопределяющие деструкторы в более производных классах. Основания и члены уничтожены в обратном порядке завершения их конструктора (см. 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).
 }

Ответ 2

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

Из Спецификация С++:

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

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

Ответ 3

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

Конструктор

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

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

Destructor

  • когда вы вызываете деструктор на производном классе по указателю или ссылке, где базовый класс имеет виртуальный деструктор, самый производный деструктор будет вызываться первым, а затем остальные производные классы в обратном порядке построения. Это необходимо для правильной очистки всей памяти. Это не сработало бы, если бы последний производный класс был вызван последним, потому что к тому времени базовый класс не существовал бы в памяти, и вы получили бы 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, и они все равно вызываются в том же порядке.

Ответ 4

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

Ответ 5

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

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

struct B : A {};

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

Ответ 6

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

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

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

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

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

Ответ 7

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