Виртуальные деструкторы
В данной функции мы применяем оператор delete:
void doit_and_bedone( vector< Query* > *pvec )
{
// ...
for ( ; it != end_it; ++it )
{
Query *pq = *it;
// ...
delete pq;
}
}
Чтобы функция выполнялась правильно, применение delete должно вызывать деструктор того класса, на который указывает pq. Следовательно, необходимо объявить деструктор Query виртуальным:
class Query {
public:
virtual ~Query() { delete _solution; }
// ...
};
Деструкторы всех производных от Query классов автоматически считаются виртуальными. doit_and_bedone() выполняется правильно.
Поведение деструктора при наследовании таково: сначала вызывается деструктор производного класса, в случае pq – виртуальная функция. По завершении вызывается деструктор непосредственного базового класса – статически. Если деструктор объявлен встроенным, то в точке вызова производится подстановка. Например, если pq указывает на объект класса AndQuery, то
delete pq;
приводит к вызову деструктора класса AndQuery за счет механизма виртуализации. После этого статически вызывается деструктор BinaryObject, а затем – снова статически – деструктор Query.
В следующей иерархии классов
class Query {
public: // ...
protected:
virtual ~Query();
// ...
};
class NotQuery : public Query {
public:
~NotQuery();
// ...
};
уровень доступа к конструктору NotQuery открытый при вызове через объект NotQuery, но защищенный – при вызове через указатель или ссылку на объект Query. Таким образом, виртуальная функция подразумевает уровень доступа того класса, через объект которого вызывается:
int main()
{
Query *pq = new NotQuery;
// ошибка: деструктор является защищенным
delete pq;
}
Эвристическое правило: если в корневом базовом классе иерархии объявлены одна или несколько виртуальных функций, рекомендуем объявлять таковым и деструктор. Однако, в отличие от конструктора базового класса, его деструктор не стоит делать защищенным.