2014-11-04 3 views
1

Я видел это раньше, но не ясно или в той же ситуации, с которой я столкнулся.C++ Не абстрактное наследование деструктора

У меня есть базовый базовый класс. Он имеет защищенный конструктор и деструктор. Он наследуется несколькими полными типами, которые также имеют публичные конструкторы и деструкторы. У меня возникла проблема, когда удаление объекта не вызывает дочерние деструкторы, если на объект ссылается базовый тип.

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     ~Tree(){ } 
}; 

class OakTree : public Tree 
{ 
public: 
     OakTree(){ } 
     ~OakTree(){ } 
}; 

vector<Tree*> Trees; // Store objects using the base type 
Trees.push_back(new OakTree()); // Create derived object 
delete Trees[0]; // OakTree desctructor does not get called 

Как я могу назвать деструктор OakTree? Я пробовал отмечать все деструкторы виртуальными, но это не сработало. Деструктор базового класса не может быть абстрактным (это позволит решить проблему вызова, но не проблему удаления).

+0

К сожалению, у меня была ошибка в пределах одного из деструкторов, вызывающих его, чтобы показать утечку памяти на моем дампа памяти заставляет меня думать, деструктор не был вызван. Я был на 99% уверен, что деструкторы должны были быть виртуальными в этом случае, но я использовал их как виртуальные, но это не помогло решить проблему утечки памяти. Небрежно с моей стороны, кроме +1 для ваших ответов. –

ответ

7

Сделайте свой деструктор базового класса virtual.

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     virtual ~Tree(){ } 
} 

В противном случае при попытке удалить с помощью указателя базового класса будет отображаться неопределенное поведение. Это немного устаревшее, но Скотт Мейерс выразил это красочно в Эффективных C++, 2-й изд:

Стандартного языка C++ необычно ясно по этой теме: при попытке удалить полученный объект класса через базовый класс указатель и базовый класс имеют неиртуальный деструктор (как EnemyTarget), результаты не определены. Это означает, что компиляторы могут генерировать код, чтобы делать все, что им нравится: переформатировать свой диск, отправить наводящий письмо своему начальнику, исходный код факса для ваших конкурентов. (Что часто происходит во время выполнения, так это то, что деструктор производного класса никогда не вызывается ...)

+0

Это на самом деле будет специфичным для реализации, и я уверен, что существующие реализации не вызывают функции наугад, особенно такие форматирующие диски. Компиляторы, которые я тестировал, называют «ближайшей вещью», которая является деструктором для указателя T. – dtech

+0

Это * гипербола *. Однако неопределенное поведение может представлять угрозу безопасности, которая в некоторых случаях может быть использована для достижения целого ряда целей злоумышленников. –

+0

Просто не думайте, что гипербола имеет какое-либо место в технической литературе, я думаю, что мистер Майерс слишком усердствует, чтобы быть «эксцентричным»/смешным и излишне красочным во многих своих высказываниях. Я вырос, чтобы ненавидеть такое поведение из-за внимания, жаждущего профессоров на лекциях, которые, как правило, тратят больше времени на культивирование причудливого образа перед «впечатлительной молодежью», чем на самом деле преподавание предмета под рукой. – dtech

3

Это потому, что деструктор вашего базового класса не объявлен виртуальным.

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     virtual ~Tree(){ } 
} 
0

Если вы собираетесь использовать полиморфизм, вы должны иметь виртуальный деструктор в базовом классе.

Проблема заключается в том, что при вызове delete на Tree * никто не знает, что это за дерево, и, имея виртуальный деструктор, компилятор генерирует вызов через указатель, который указывает на таблицу для функций для этого конкретного объекта экземпляр, и вы получите деструктор для любого типа.

В противном случае стандарт говорит, что это неопределенное поведение, но то, что, похоже, происходит в большинстве случаев, - это компилятор, генерирующий вызов деструктора любого типа, в котором ваш полиморфный указатель или ссылка, в вашем случае, является базовым классом ,

0

Интересно, почему никто на самом деле не спрашивает, что автор пытается достичь с помощью кода?

Хотя, да, имея виртуальный деструктор в базовом классе поможет вам получить ваш деструктор называется, что вы по-прежнему остается в неопределенное поведение.

Что вы ожидаете после строки delete Trees[0];?

Или, скорее, чего вы пытаетесь достичь с помощью этой линии?

Если вы пытаетесь удалить элемент из вектора, вы можете:

Trees.erase(Trees.begin()); 

Но тогда, вы остаетесь с утечкой памяти, если вы забыть удаления строки.

Следовательно, не используйте простые указатели в вашем векторе. Рассмотрим:

std::vector< std::shared_ptr<Tree> > Trees; 
Trees.push_back(std::make_shared<OakTree>()); 
Trees.erase(Trees.begin()); 

Runnable @Ideone

+0

Это упрощенный код из более крупного проекта. Базовый класс хранит некоторые данные, а производные классы манипулируют этими данными различными способами. У производных классов также есть локальные ресурсы, которые необходимо удалить в конце срока действия объекта. Я знаю, как работают векторы, и это не было частью вопроса. –

+0

как насчет управления памятью? Если бы это было только для деструктора, вам не нужен какой-либо векторный код в вашем вопросе. –

+0

Просто обратим внимание на то, что я храню его как 'Tree *', а не 'OakTree *' или 'Tree'. Давайте не будем слишком придирчивыми. Ответ на этот вопрос. –

Смежные вопросы