Виртуальные деструкторы
В данной функции мы применяем оператор 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; |
}
Эвристическое правило: если в корневом базовом классе иерархии объявлены одна или несколько виртуальных функций, рекомендуем объявлять таковым и деструктор. Однако, в отличие от конструктора базового класса, его деструктор не стоит делать защищенным.