Виртуальные деструкторыВ принципе, в практически любой мало-мальски толковой книге по C++ рассказывается, зачем нужны виртуальные деструкторы и почему их надо использовать. Тем не менее, как показывает практика, ошибка, связанная с отсутствием виртуальных деструкторов, настолько распространена, что я решил еще раз рассказать о ней. Итак, рассмотрим маленький пример:
class A
{
public:
virtual void f() = 0;
~A();
};
class B : public A
{
public:
virtual void f();
~B();
};
Вызов компилятора gcc строкой g++ -c -Wall test.cppДаст следующий результат: test.cpp:6: warning: `class A' has virtual functions but non-virtual destructortest.cpp:13: warning: `class B' has virtual functions but non-virtual destructorЭто только предупреждения, компиляция прошла вполне успешно. Тем не менее, почему же gcc выдает подобные предупреждения? Все дело в том, что виртуальные функции используются в C++ для обеспчения полиморфизма --- т.е., клиентская функция вида:
void call_f(A* a)
{
a->f();
}
никогда не "знает" о том, что конкретно сделает вызов метода std::vector<A*> a_collection; a_collection.push_back(new B());
В результате такого кода теряется информация о том, чем конкретно является
каждый из элементов for(std::vector<A*>::iterator i = ... ) delete *i;
все объекты, содержащиеся в
В этом можно убедиться, если соответствующим
образом определить деструкторы классов
inline A::~A()
{
puts("A::~A()");
}
inline B::~B()
{
puts("B::~B()");
}
Тогда выполнение следующего кода: A* ptr = new B(); delete ptr; Приведет к следующему результату: A::~A()
Если же в определении класса B::~B() A::~A() В принципе, все сказано. Но, несмотря на это, очень многие программисты все равно не создают виртуальных деструкторов. Одно из распространенных заблуждений --- виртуальный деструктор нужен лишь в том случае, когда на деструктор порожденных классов возлагаются какие-то нестандартные функции; если же функционально деструктор порожденного класса ничем не отличается от деструктора предка, то делать его виртуальным совершенно необязательно. Это неправда, потому что даже если деструктор никаких специальных действий не выполняет, он все равно должен быть виртуальным, иначе не будут вызваны деструкторы для объектов-членов класса, которые появились по отношению к предку. То есть:
#include <stdio.h>
class A
{
public:
A(const char* n);
~A();
protected:
const char* name;
};
inline A::A(const char* n) : name(n)
{ }
inline A::~A()
{
printf("A::~A() for %s.\n", name);
}
class B
{
public:
virtual void f();
B();
~B();
protected:
A a1;
};
inline B::~B()
{ }
inline B::B() : a1("a1")
{ }
void B::f() { }
class C : public B
{
public:
C();
protected:
A a2;
};
inline C::C() : a2("a2")
{ }
int main()
{
B* ptr = new C();
delete ptr;
return 0;
}
Компиляция этого примера проходит без ошибок (но с предупреждениями), вывод программы следующий: A::~A() for a1.
Немного не то, что ожидалось? Тогда поставим перед названием деструктора
класса A::~A() for a2. A::~A() for a1. Теперь вывод программы несколько более соответствует действительности. РезюмеСтрого говоря, очень сложно придумать пример, когда существует иерархия классов и в ней отсутствует виртуальный деструктор (и при этом не совершена ошибка). Во всяком случае, это очень странно и подобное решение требует основательных комментариев к исходному тексту.
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
© 2000-2008, Andrey L. Kalinin mailto:andrey@kalinin.ru |
|