Различие между деструктором (конечно, также конструктор) и другими функциями членства - то, что, если функция постоянного участника имеет тело в производном классе, только версия в Производном классе выполняется. Принимая во внимание, что в случае деструкторов, обе полученных, а также версии базового класса выполняются?
Будет замечательно знать то, что точно происходит в случае деструктора (возможно, виртуальный) и конструктор, которым их называют для всех его базовых классов, даже если большая часть объекта производного класса удалена.
Заранее спасибо!
Стандарт гласит:
После выполнения тела деструктора и уничтожения любых автоматических объектов, размещенных в теле, деструктор для класса 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).
}
Конструктор и деструктор отличаются от остальных обычных методов.
Конструктор
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) {} };
Деструктор:
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
и будут вызываться в том же порядке.
Потому что так работают dtor'ы. Когда вы создаете объект, ctors вызываются, начиная с базового и до самого производного. Когда вы уничтожаете объекты (правильно), происходит обратное. Виртуальность dtor имеет значение, если/когда вы уничтожаете объект через указатель (или ссылку, хотя это довольно необычно) на базовый тип. В этом случае альтернатива заключается не в том, что вызывается только производный dtor - скорее, альтернативой является просто неопределенное поведение. Это может принять форму вызова только производного dtor, но может принять и совершенно другую форму.
Как говорит Игорь, конструкторы должны вызываться для базовых классов. Рассмотрим, что произойдет, если он не будет вызван:
struct A {
std::string s;
virtual ~A() {}
};
struct B : A {};
Если деструктор для A
не будет вызван при удалении экземпляра B
, A
никогда не будет очищен.
Деструктор базового класса может отвечать за очистку ресурсов, которые были выделены конструктором базового класса.
Если ваш базовый класс имеет конструктор по умолчанию (тот, который не принимает параметры или имеет значения по умолчанию для всех своих параметров), этот конструктор автоматически вызывается при создании производного экземпляра.
Если ваш базовый класс имеет конструктор, требующий параметров, вы должны вызвать его вручную в списке инициализаторов конструктора производного класса.
Деструктор вашего базового класса всегда будет автоматически вызываться при удалении производного экземпляра, поскольку деструкторы не принимают параметров.
Если вы используете полиморфизм и на ваш производный экземпляр указывает указатель базового класса, то деструктор производного класса вызывается только в том случае, если деструктор базового класса является виртуальным.
Когда любой объект уничтожается, деструкторы запускаются для всех подобъектов. Это включает как повторное использование путем включения, так и повторное использование путем наследования.
Это сделано специально. Деструктор базового класса должен быть вызван для того, чтобы он освободил свои ресурсы. Эмпирическое правило гласит, что производный класс должен очищать только свои собственные ресурсы и оставлять базовому классу возможность очищать себя самостоятельно.
Из C++ spec:
После выполнения тела функции деструктора и уничтожения любых автоматические объекты, выделенные внутри тела, деструктор для класса X вызывает деструкторы для прямых членов, деструкторы для прямых базовых классов и, если X является тип самого производного класса (12.6.2), его деструктор вызывает деструкторы виртуальных базовых классов X классов. Все деструкторы вызываются как если бы на них ссылались с квалифицированное имя, то есть, игнорируя любые возможные виртуальные переопределения деструкторы в более производных классах. Базовые элементы и члены уничтожаются в обратном порядке завершения их конструктора (см. 12.6.2).
Кроме того, поскольку существует только один деструктор, нет никакой двусмысленности относительно того, какой деструктор должен вызывать класс. Это не относится к конструкторам, где программист должен выбирать, какой конструктор базового класса должен быть вызван, если нет доступного конструктора по умолчанию.