Вопрос новичка про виртуальный деструктор
От: cppNewbie  
Дата: 30.04.04 11:23
Оценка:
Если инициализировать указатель родительского класса адресом объекта дочернего класса и объявить в родительском классе деструктор виртуальным, то при "разрушении" дочернего объекта через этот указатель сначала выполнится деструктор дочернего класса, а потом деструктор родителя. То, что выполняется деструктор дочернего объекта — мне понятно, потому что мы объявили деструктор родителя виртуальным. Но почему выполняется деструктор родительского класса? Есть ли этому логичное объяснение или это нужно принять как данность? Ведь при выполнении метода через указатель или ссылку родительского типа, если одноименный метод в предке объявлен виртуальным, исполняется только один метод, тот, который принадлежит дочернему объекту.

Поделитесь, пожалуйста!
Re: Вопрос новичка про виртуальный деструктор
От: _AK_ Россия  
Дата: 30.04.04 11:32
Оценка: +1
Здравствуйте, cppNewbie, Вы писали:

N>Если инициализировать указатель родительского класса адресом объекта дочернего класса и объявить в родительском классе деструктор виртуальным, то при "разрушении" дочернего объекта через этот указатель сначала выполнится деструктор дочернего класса, а потом деструктор родителя. То, что выполняется деструктор дочернего объекта — мне понятно, потому что мы объявили деструктор родителя виртуальным. Но почему выполняется деструктор родительского класса? Есть ли этому логичное объяснение или это нужно принять как данность? Ведь при выполнении метода через указатель или ссылку родительского типа, если одноименный метод в предке объявлен виртуальным, исполняется только один метод, тот, который принадлежит дочернему объекту.


N>Поделитесь, пожалуйста!


вот одно из логических оъяснений:


struct Parent
{
  Parent() : m_Pointer(new int) {}
  virtual ~Parent()
  {
    delete m_Pointer;
  }
  int* m_Pointer;
};

struct Child : Parent
{
};

int main()
{
  Parent* obj = new Child;
  delete obj; // <- если тут не вызовется деструктор класса Parent, то у нас будет mem-leak.
}
Re: Вопрос новичка про виртуальный деструктор
От: Mr. None Россия http://mrnone.blogspot.com
Дата: 30.04.04 11:41
Оценка:
Здравствуйте, cppNewbie, Вы писали:

N>Если инициализировать указатель родительского класса адресом объекта дочернего класса и объявить в родительском классе деструктор виртуальным, то при "разрушении" дочернего объекта через этот указатель сначала выполнится деструктор дочернего класса, а потом деструктор родителя. То, что выполняется деструктор дочернего объекта — мне понятно, потому что мы объявили деструктор родителя виртуальным. Но почему выполняется деструктор родительского класса? Есть ли этому логичное объяснение или это нужно принять как данность? Ведь при выполнении метода через указатель или ссылку родительского типа, если одноименный метод в предке объявлен виртуальным, исполняется только один метод, тот, который принадлежит дочернему объекту.


N>Поделитесь, пожалуйста!


Просто это и есть нормально поведение при вызове деструктора дочернего объекта вне зависимости от того, объявлен деструктор виртуальным или нет...
В псевдо-коде это будет выглядеть так:
class ParentClass
{
public:
    virtual ~ParentClass()
    {
        // Удаление родительского объекта
    }
};

class ChildClass
{
public:
    virtual ~ChildClass()
    {
        // Удаление дочернего объекта
        ~ParentClass();
        // Данный вызов не зависит от наличия ключевого слова virtual, можно принять, что он просто
        // вставляется компилятором
    }
};


Точно также вызываются конструкторы родительских классов перед вызовом конструктора дочернего.
Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.
Re: Вопрос новичка про виртуальный деструктор
От: Alexmoon Украина  
Дата: 30.04.04 11:46
Оценка:
Здравствуйте, cppNewbie, Вы писали:

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

Правильно инициализировать собственные поля может только твой собственный тип и как правильно освободить все занятые ресурсы. Поэтому по цепи вызываются все конструкторы и в обратном порядке все деструкторы, если сделано все правильно. А с виртуальным наследованием все не намного но еще сложнее. Лучше Страуструпа почитать.
Re[2]: Вопрос новичка про виртуальный деструктор
От: unrealalex Россия  
Дата: 30.04.04 11:50
Оценка:
Здравствуйте, _AK_, Вы писали:
class Parent
{
public:
  Parent(){};
  virtual ~Parent(){};
    
private:
  Parent(const Parent&);
  Parent& operator=(const Parent&);
};

class Child: public Parent
{
public:
  Child(): m_Pointer(new int) {}
  virtual ~Child()
  {
    delete m_Pointer;
  }
    
private:
  int* m_Pointer;
    
  Child(const Child&);
  Child& operator=(const Child&);
};

int main()
{
  Parent* obj = new Child();
  delete obj; // <- если тут не вызовется деструктор класса Child, то у нас будет mem-leak.
}

так ИМХО будет вернее — деструктор Parent вызоветься всегда, ведь мы же удаляем Parent*.
Невозможное мы сделаем сегодня — чудо займет немного больше времени. /Аноним/
Вопрос новичка про виртуальный деструктор
От: Кодт Россия  
Дата: 30.04.04 18:26
Оценка: 1 (1)
#Имя: FAQ.cpp.virtualdtor
Здравствуйте, cppNewbie, Вы писали:

N>Если инициализировать указатель родительского класса адресом объекта дочернего класса и объявить в родительском классе деструктор виртуальным, то при "разрушении" дочернего объекта через этот указатель сначала выполнится деструктор дочернего класса, а потом деструктор родителя. То, что выполняется деструктор дочернего объекта — мне понятно, потому что мы объявили деструктор родителя виртуальным. Но почему выполняется деструктор родительского класса? Есть ли этому логичное объяснение или это нужно принять как данность? Ведь при выполнении метода через указатель или ссылку родительского типа, если одноименный метод в предке объявлен виртуальным, исполняется только один метод, тот, который принадлежит дочернему объекту.


Чем деструктор отличается от любой другой функции...
Деструктор сперва выполняет код, написанный в { ... }, затем деструкторы всех членов-данных, затем деструкторы всех обычных баз, затем деструкторы всех виртуальных баз.
Вне зависимости от того, виртуальный деструктор или не виртуальный.

Зачем нужна виртуальность?
class Base { ... };

class Child : public Base { ... };

main()
{
  Base* ptr = new Child;

  ...

  delete ptr; // ***
}

В точке *** у объекта *ptr вызывается деструктор класса Base (поскольку про то, Child там или не Child — компилятор не знает).
Если деструктор невиртуальный, то вызовется только деструктор базы — значит, всё, что должен выполнить деструктор Child'а, не произойдёт.
А если виртуальный — то из VMT возьмут адрес актуального деструктора.
Перекуём баги на фичи!
Re: Вопрос новичка про виртуальный деструктор
От: Шахтер Интернет  
Дата: 01.05.04 01:59
Оценка: 2 (1)
Здравствуйте, cppNewbie, Вы писали:

N>Если инициализировать указатель родительского класса адресом объекта дочернего класса и объявить в родительском классе деструктор виртуальным, то при "разрушении" дочернего объекта через этот указатель сначала выполнится деструктор дочернего класса, а потом деструктор родителя. То, что выполняется деструктор дочернего объекта — мне понятно, потому что мы объявили деструктор родителя виртуальным. Но почему выполняется деструктор родительского класса? Есть ли этому логичное объяснение или это нужно принять как данность?



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


Замечательно, это ты понимаешь логически. А теперь маленькое усилие -- деструктор объявлен виртуальным. Значит, должен быть вызван деструктор производного класса. По той же самой причине -- он виртуальный. Но этот деструктор (независимо от своей виртуальности) неявно вызывает деструктор базового класса -- потому что назначение деструктора в том, чтобы "разобрать" класс, а для этого нужно, в частности, "разобрать" базовый класс.

N>Поделитесь, пожалуйста!
... << RSDN@Home 1.1.0 stable >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: Вопрос новичка про виртуальный деструктор
От: Herclia США  
Дата: 01.05.04 06:50
Оценка: 59 (5) +1
Здравствуйте, cppNewbie, Вы писали:

Уважаемый cppNewbie,

Давайте начнем не с кода, а с примера:

У Вас 2 цилиндра, один вложен в другой, если Вы захотите
использовать первый цилиндр, от второго придется освободится (ввиду
возможных неоднозначностей, цилиндры находятся в вертикальном
положении, т.е. стоят на чем-то). В каком порядке ? В порядке
подчиняющемуся закону стека: последним пришел, первым вышел. Иначе
никак, второй придется вынуть из первого. Теперь последовательно
рассмотрим возможные варианты, вызова кострукторов и деструкторов.

=======================================================================
Пример N%1, нет виртуальности в деструкторах, как впрочем нет и
"Если инициализировать указатель родительского класса адресом объекта
дочернего класса ", просто наследование:
#include "stdafx.h"
#include <iostream.h>

class A {
public:
    A()        { cout << "constructor  A()\n"; }
    ~A()    { cout << "destructor  ~A()\n"; }
};

class B : public A {
public:
    B()        { cout << "constructor  B()\n"; }
    ~B()    { cout << "destructor  ~B()\n"; }
};

void main( void )
{
    B* ptrB = new B();
    delete ptrB;
}

Результат:
constructor  A()
constructor  B()
destructor  ~B()
destructor  ~A()

Все понятно верно ? Один конструктор вызывает другой, деструкторы
работают по правилу — стек... Все прекрасно и удивительно, но где и как
это ВИДНО !!! Какого рожна это работает именно так, а не по другому.
Давайте расставим все точки над i.

Кострукторы и множественное наследование.
class A {};
class B {};
class C : public A, B {};

Итак конструктор класса C:
C::C() : A(), B()
{
    ...// дополнительная инициализация.
}

последовательно вызывает конструкторы классов A(), B(). Другими
словами вызов конструкторов классов предков происходит ДО !!!
чтения первого оператора тела конструктора потомка.

Думаю, логика в вызове конструкторов именно с таким алгоритмом
прозрачна, свяжем с цилиндрами: чтобы понять, что из себя
представляает цилиндр А, необходимо рассмотреть цилиндр C, убрать его,
рассмотреть цилиндр B, убрать его, увидев наконец цилиндр А. По
другому если мы хотим достучатся до внутренности цилиндра A, мы должны
последовательно вынуть C, затем B.
Что происходит с деструкторами, обратная ситуация... Если Вы в
трубу затолкали последовательно черный мяч, затем белый, то что бы
снова лицезреть черный мяч, необходимо вынуть белый...
Видимо поэтому для графического представления наследования используют
стрелки влево(или вверх):
  A
   <--B
       <--C

======================================================================
Пример N%2, снова нет виртуальности в деструкторах, НО "Если
инициализировать указатель родительского класса адресом объекта
дочернего класса ", есть:
class A {
public:
    A()        { cout << "constructor  A()\n"; }
    ~A()    { cout << "destructor  ~A()\n"; }
};

class B : public A {
public:
    B()        { cout << "constructor  B()\n"; }
    ~B()    { cout << "destructor  ~B()\n"; }
};

void main( void )
{
    B* ptr = new А();
    delete ptr;
}

Результат:
constructor  A()
constructor  B()
destructor  ~A()

У нас зависла строка "destructor ~B()", другими словами деструктор
класса B не выполнился. Как пишет Джефф Элджер "... переменная
превращается в "Летучего Голландца", обреченного на вечные скитания в
памяти. Чтобы избежать беды, достаточно обьявить оба деструктора
виртуальными, в этом случае независимо от типа указателя ( кроме конечно
void* ) уничтожение будет начинаться с класса потомка...".
И все ВСЕ !!! Но почему ? Это еще один постулат не более !!!
На самом деле ответ в этой фразе есть, просто Д. Элджер не
раскрывает его в полной мере — "...независимо от типа указателя...",
другими словами, через какой класс выражен наш указатель("ptr" —
пример N%2). Он относится к классу B или А ? Конечно к классу А, скажете
Вы, потому-что... Стоп, без потому-что. Я не верю, что это класс A, как
проверить... Просто напишем в каждом классе функцию с одинаковым имемен,
посмотрим, функция какого класса сработает ! Это раз, два наш пример с
цилиндрами перестает работать на данном этапе, потому как мы подменяем
один цилиндр другим, что в природе без "волшебства" сделать невозможно.

======================================================================
Пример N%3, компилятор ругается но работает, мы же перекрываем
функцию родительского класса:
class A {
public:
    foo() { cout << "Call function of class A \n"; }
};

class B : public A {
public:
    foo() { cout << "Call function of class B \n"; }
};

void main( void )
{
    A* ptr = new B();
    ptr->foo();
    delete ptr;
}

Результат:
Call function of class A,

Итак вы были правы, в примере N%2, создается указатель на класс A.

Итак мы вплотную подошли к понятию полиморфизма — двуликости
указателя на какой-то класс. Имеется ввиду следущее, если мы перед
функцией foo() в базовом классе A поставим virtual, картина
кардинальным образом изменится.

======================================================================
Пример N%4, компилятор не ругается все корректно, но ptr —
указыбает на класс B !!!:
class A {
public:
    virtual foo() { cout << "Call function of class A \n"; }
};

class B : public A {
public:
    foo() { cout << "Call function of class B \n"; }
};

void main( void )
{
    A* ptr = new B();
    ptr->foo();
    delete ptr;
}

Результат:
Call function of class B.


======================================================================
Пример N%5, данный пример составлен по аналогии с примером N%4:
#include "stdafx.h"
#include <iostream.h>

class A {
public:
    virtual ~A()    { cout << "destructor  ~A()\n"; }
};

class B : public A {
public:
    ~B()    { cout << "destructor  ~B()\n"; }
};

Результат:
destructor  ~B()
destructor  ~A()

Ну вот собственно говоря и все... Разговор свелся к понятию полиморфизма,
стеку и виртуальным функциям.

Уважаемый cppNewbie, C++ полон неоднозначностей, поэтому, примите
мой скромный совет, постарайтесь, в ситуациях, когда ответ не лежит на
поверхности, искать аналогии в жизни, это часто наводит на решение
проблемы, ведь "...Речь идет не о не о строении компьютерного языка, а
скорее о нашем строении. Все уродства C++ — это в основном наши
уродства..." Джефф Элджер.
Удачи...
Исправлено форматирование. Не забывайте, пожалуйста, пользоваться тегами [c]...[/c], [code]....[/code] и т.п. для выделения фрагментов кода. -- ПК
Re[2]: Спасибо.
От: cppNewbie  
Дата: 03.05.04 15:36
Оценка:
Спасибо, Шахтер. Хоть смысл ответа на мой вопрос до вас пытались донести другие "форумчане" (спасибо им за это большое!), ваш ответ был для меня наиболее ясен и, в то же время, достаточно лаконичен. Благодарю.
Re[2]: Вопрос новичка про виртуальный деструктор
От: cppNewbie  
Дата: 03.05.04 20:17
Оценка:
Здравствуйте, Herclia, Вы писали:

H>Давайте начнем не с кода, а с примера...


Я потрясен, Herclia, столь подробным ответом. Примите мою благодарность и уважение.
Re: [moderator] От модератора форума "C/C++"
От: Павел Кузнецов  
Дата: 05.05.04 21:01
Оценка: +1
Ветка
Автор: VladD2
Дата: 01.05.04
о граблях, нелогичности стандарта и прочих общефилософских моментах перенесена в форум "Священные войны".
--
ПК
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[2]: Вопрос новичка про виртуальный деструктор
От: Agent Smith Россия  
Дата: 02.02.07 14:10
Оценка:
Herclia,
в примере 2 небольшая опечатка. Вместо
B* ptr = new А();

видимо, имелось в виду:
A* ptr = new B();

В остальном — очень хорошо
Never argue with a woman who reads. It's likely she can also think. (c)
Re[3]: Вопрос новичка про виртуальный деструктор
От: i-maverick Россия  
Дата: 02.02.07 14:18
Оценка:
Здравствуйте, Agent Smith, Вы писали:

AS>Herclia,

AS>в примере 2 небольшая опечатка. Вместо

А чего такой свежий пост выбрал? Взял бы что-нибудь из 80-ых...
Re[4]: Вопрос новичка про виртуальный деструктор
От: Agent Smith Россия  
Дата: 02.02.07 14:22
Оценка:
Здравствуйте, i-maverick, Вы писали:

IM>Здравствуйте, Agent Smith, Вы писали:


AS>>Herclia,

AS>>в примере 2 небольшая опечатка. Вместо

IM>А чего такой свежий пост выбрал? Взял бы что-нибудь из 80-ых...


потому что один из постов данной темы находится в разделе Статьи
Автор: Кодт
Дата: 30.04.04

Собственно, так я на него и вышел )
Never argue with a woman who reads. It's likely she can also think. (c)
Re[3]: Вопрос новичка про виртуальный деструктор
От: Аноним  
Дата: 02.02.07 19:41
Оценка:
Здравствуйте, Agent Smith, Вы писали:

AS>Herclia,

AS>в примере 2 небольшая опечатка. Вместо
AS>
AS>B* ptr = new А();
AS>

AS>видимо, имелось в виду:
AS>
AS>A* ptr = new B();
AS>

AS>В остальном — очень хорошо


Уважаемый Agent Smith,

Вы правы нельзя создать обьект порожденного класса через базовый —
так как он не имеет функциональности вновь создаваемого.

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

Каждый студент — человек, но не каждый человек — студент.

Благодарю за корректировку.

Herclia
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.