А всё потому, что выделенный блок памяти (0x09ac9008) имеет отрицательное смещение от собственно указателя на первый элемент (0x09ac900c). Деструктор первого элемента отрабатывает нормально, а вот собственно освобождение памяти обламывается.
Здравствуйте, Шебеко Евгений, Вы писали:
ШЕ>Сейчас приходится собеседовать много новичков. ШЕ>Заметил что ничего в жизни не меняется. ШЕ>Даже люди с опытом работы в крупных наступают на те же грабли, на которые наступал я в своё время.
Зачем все это?
Как уже резонно заметили выше, новички которые это не знают и им это неинтересно — просто ничего не поймут.
Те, кто заинтересовался хорошими С++ практиками — быстро найдут ответы в книгах Саттер/Майерса/Александреску.
Шансы на то, что данный топ прочитают новички — минимален. Так что данный топик разве что вызвать C++ флуд очередной и померяться писюнами.
Здравствуйте, Кодт, Вы писали:
К>Саттерс — это семья Саттеров?
К>Кстати, напоминаю о существовании правил форума, где попрекание собеседников нехваткой квалификации не приветствуется.
Нет, ну "не гуру" это не нехватка квалификации. А то так можно дойти до того, что сказать человеку, что он не гений, будет восприниматься как оскорбление.
Здравствуйте, kov_serg, Вы писали:
_>Деструкторы записывают обратно свою таблицу виртуальных функций.
не важно кто что и куда записывает, вирутальные функции вызванные из конструктора или деструктора вызываются как невиртуальные, самый простой способ не наступать на грабли: "не делайте так" (используйте двухфазную инициализацию/удаление).
Здравствуйте, XuMuK, Вы писали:
XMK>Здравствуйте, kov_serg, Вы писали:
_>>Деструкторы записывают обратно свою таблицу виртуальных функций.
XMK>не важно кто что и куда записывает, вирутальные функции вызванные из конструктора или деструктора вызываются как невиртуальные, самый простой способ не наступать на грабли: "не делайте так" (используйте двухфазную инициализацию/удаление).
Ептить. Я же не говорю что это правильно. Я говорю что это неявная грабля на которую можно наступить. И она заложена в дизайне VCL и MFC.
Здравствуйте, Masterkent, Вы писали:
M>XuMuK:
XMK>>вирутальные функции вызванные из конструктора или деструктора вызываются как невиртуальные
M>Вызовы там виртуальные, только final overriders определяются специфически.
M>
Здравствуйте, XuMuK, Вы писали:
_>>Деструкторы записывают обратно свою таблицу виртуальных функций.
XMK>не важно кто что и куда записывает, вирутальные функции вызванные из конструктора или деструктора вызываются как невиртуальные,
Функции там вызываются как виртуальные — но только для текущего типа, т.е. для базы.
Насколько компилятору хватает поля зрения, он подменяет виртуальный вызов статическим.
struct base
{
virtual void f() {}
void g() { f(); } // здесь будет заведомо виртуальный вызов (хотя, компилятор может проинлайнить g()...)
base() { f(); g(); } // по крайней мере единожды будет виртуальный вызов f() - из g()
};
XMK> самый простой способ не наступать на грабли: "не делайте так" (используйте двухфазную инициализацию/удаление).
"Не делайте так" здесь значит более общее: не лезьте в разрушаемый объект, особенно — из другого потока, особенно — оставаясь в функции-члене разрушаемого объекта (впрочем, есть способ отстрелить себе ногу и в однопоточном режиме и даже без виртуальных функций).
Здравствуйте, kov_serg, Вы писали:
_>В конструкторе всегда таблица от текущего класса и в деструкторе тоже. _>Именно эта особенность приводит к граблям при параллельном исполнении.
На самом деле, грабли там в кривом дизайне и перекрытии времени жизни.
Ну нельзя делать так, что время жизни объекта-работодателя (до входа в деструктор) было короче, чем время жизни потока-подчинённого.
А если очень-очень хочется задействовать RAII, чтобы поток гасился из деструктора, — так не надо смешивать объект-работодатель и RAII-сторож.
Да, получится двухфазная деинициализация.
И вообще, ООП конкретно в этом месте больше мешает, чем помогает, — провоцируя на антипаттерн самоубийцы. ФП-подход, где данные для потока передаются в замыкание и там живут до самой смерти, — более естественнен.
Не всплывёт его бедное тело, грабли на дне я поставил умело.
Антипаттерн "самоубийца" зашит в библиотеку C'est la vie. (Et la MORT!!!)
Имхо, лучшее, что можно сделать здесь — это использовать TThread/CWinThread исключительно как хэндл потока, и никогда от них не наследоваться.
Не приходит же в голову (или приходит? после стольких лет с дебилдером и мфц?) наследоваться от boost/std::thread ?