Rambler's Top100 Service Этот текст распечатан с домашней странички Андрея Калинина (www.kalinin.ru).
Оригинал статьи находится по этому адресу: http://www.kalinin.ru/programming/cpp/28_07_00.shtml


Виртуальные деструкторы

28.07.00

В принципе, в практически любой мало-мальски толковой книге по 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 destructor
test.cpp:13: warning: `class B' has virtual functions but non-virtual destructor

Это только предупреждения, компиляция прошла вполне успешно. Тем не менее, почему же gcc выдает подобные предупреждения?

Все дело в том, что виртуальные функции используются в C++ для обеспчения полиморфизма --- т.е., клиентская функция вида:

void call_f(A* a)
{
  a->f();
}

никогда не "знает" о том, что конкретно сделает вызов метода f() --- это зависит от того, какой в действительности объект представлен указателем a. Точно так же сохраняются указатели на объекты:

std::vector<A*> a_collection;
a_collection.push_back(new B());

В результате такого кода теряется информация о том, чем конкретно является каждый из элементов a_collection (я имею в виду, без использования RTTI). В данном случае это грозит тем, что при удалении объектов:

for(std::vector<A*>::iterator i = ... )
  delete *i;

все объекты, содержащиеся в a_collection, будут удалены так, как будто это --- объекты класса A.

В этом можно убедиться, если соответствующим образом определить деструкторы классов A и B:

inline A::~A()
{
  puts("A::~A()");
}

inline B::~B()
{
  puts("B::~B()");
}

Тогда выполнение следующего кода:

A* ptr = new B();
delete ptr;

Приведет к следующему результату:

A::~A()

Если же в определении класса A деструктор был бы сделан виртуальным (virtual ~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.

Немного не то, что ожидалось? Тогда поставим перед названием деструктора класса B слово virtual. Результат изменится:

A::~A() for a2.
A::~A() for a1.

Теперь вывод программы несколько более соответствует действительности.

Резюме

Строго говоря, очень сложно придумать пример, когда существует иерархия классов и в ней отсутствует виртуальный деструктор (и при этом не совершена ошибка). Во всяком случае, это очень странно и подобное решение требует основательных комментариев к исходному тексту.

Ссылки по теме

Бъерн Страуструп Язык программирования C++, 3 издание.

©2000-2001 by Andrey L. Kalinin,
andrey@kalinin.ru