- 1
- 2
- 3
- 4
- 5
THTTPServer::TDynamicResponse::~TDynamicResponse( void )
{
if(typeid(*this)==typeid(TDynamicResponse))//Борьба с pure virtual function call.
this->flush();
};
Нашли или выдавили из себя код, который нельзя назвать нормальным, на который без улыбки не взглянешь? Не торопитесь его удалять или рефакторить, — запостите его на говнокод.ру, посмеёмся вместе!
+163
THTTPServer::TDynamicResponse::~TDynamicResponse( void )
{
if(typeid(*this)==typeid(TDynamicResponse))//Борьба с pure virtual function call.
this->flush();
};
Проект поменьше.
Просто flush - виртуальная функция и если вызвать flush в деструкторе предка (а не в деструкторе реального объекта), то вызывается не тот flush, тк реальный объект к этому времени деструктирован.
Автор написал такой воркэраунд вокруг этой проблемы.
Я пока это не переписывал и пока не знаю как, но планирую.
http://ideone.com/0jc2b
воркэраунд ни о чем, это просто изначально идиотская идея пытаться в деструктор предка засунуть несвойственные ему задачи - якобы пытаться в нем выполнить виртуальные операции, за которые он никакой ответственности нести не имеет права, а потом с этим героически бороться
Капитанские погоны вам к лицу.
>воркэраунд ни о чем
Как не о чем? Свою задачу выполняет. Вызывается именно flush реального типа объекта, а не base. Добиваются этого именно копипастой этих магических строк и заменой TDynamicResponse на имя деcтруктора без символа ~.
Если знаете как это исправить правильно, то говорите, не стесняйтесь.
>> //Борьба с pure virtual function call
> Капитанские погоны вам к лицу.
не к лицу, просто неясность изъявления ваших мыслей привела меня к выводу, что работа деструкторов для кого то хранит слишком много тайн
если идея в том, что каждый предок имеет свой собственный flush, который своим вызовом должен исключить дальнейший вызов flush предка в деструкторе, то гораздо проще этого добиться с помощью protected: bool was_flushed_ в базовом классе и вызовом с банальной проверкой if (!was_flushed_) { this->flush(); was_flushed_ = true; }
но, если честно, подобные махинации в деструкторах уже наводят на мысль что в рахитектуре изначально что то пошло не так
> Свою задачу выполняет
каким образом он может выполнить свою задачу, если в деструкторе объекта ~sometype() равенство typeid(*this) == typeid(sometype) - это тождество?
http://ideone.com/cME4z
http://ideone.com/cME4z
А вот это уже интересно... В студии выполняется, а в GCC нет... Кто прав? GCC или студия 2008?
Microsoft Visual Studio 2008
Версия 9.0.30729.1 SP
но если в другом компиляторе будет иное поведение, то это лишний раз доказывает некорректность притягивания RTTI к решению проблемы в деструкторах
OMG, по непонятной причине работает - не буду трогать... :-[
Тарас, заметь: даже программисты на цпп со стажем не понимают, почему оно работает. Вот уж действительно КРЕСТОПРОБЛЕМЫ™. Хотя я недавно собеседовал Java-программиста с 6-летним стажем, не знающего элементарных вещей.
RTTI вообще сам по себе тот еще костыль, и очень вендор-специфик
запросто ожидаются даже проблемы с нерабочим dynamic_cast<>, если библиотеку и приложение собрать разными версиями одного компилятора
да и в конструкторах и деструкторах RTTI противопоказан, очень неудачная идея его там использовать
Меня это тоже раздражает... Неужели стандартизаторы С++ до сих пор не смогли стандартизировать ABI для RTTI? Казалось бы dynamic_cast - конструкция языка, но вдруг неожиданного может перестать работать, если взять более новую версию библиотеки.
это всего лишь костыль, призванный в сжатые сроки ставить подпорки в разваливающуюся глиняную рахитектуру приложения, поэтому в хорошем проекте grep -r "dynamic_cast" обязан выдать 0 результатов даже на миллионе LoC
Use Lisp, Luke.
virtual integraltype get_type() const;
void do_multi(base * b1, base * b2) и дальше уже как фантазия разыграется делать соответствие make_pair(b1->get_type(), b2->get_type()) и методу
в любом случае С++ мультиметод не будет таким красивым, как ему положено быть в приспособленных для этого языках
Можно пример?
возможно будет выглядеть красивее, чем
впрочем, я не знаю как там у вас делаются мультиметоды, потому что у меня их нет вовсе
Тут вся сложность в том, что несколько классов могут по ошибке заявить, что они type_eatable и поэтому они не верно будут обрабатываться, что возможно заметят далеко не сразу.
Что-то я начинаю думать (извините, но я реально так начинаю думать), что РАИИ и ООП несовместимы. РАИИ оставьте для всякой хрени, которая маскируется под встроенный тип, а ООП с РТТИ, наследованиями и всяким динамоговном лучше оставить ссылочным и без всякой неявной херни вообще, т.е. с ручный конструктором и деструктором.
Можно заворачивать говнообъект в РАИИ-указатель, можно подключать ГЦ, действующий только для таких объектов.
А какая разница ссылочные типы или не ссылочные? Будет та же проблема, что видна в этом топике. А уж тем более без разницы ручные деструкторы или автоматические.
Вызываться будет только то, что вызвалось.
Желающие пусть сами заводят структуру, содержащую класс и вызывающую виртуальный метод flush в деструкторе этой структуры. И flush вызовется лишь 1 раз.
ммм, это каких же?
каким образом он может выполнить свою задачу, если в деструкторе объекта ~sometype() равенство typeid(*this) == typeid(sometype) - это тождество?
Я понимаю о чем вы, просто автор кода видимо думал, что в деструкторе\конструкторе typeid работает также, как и везде.
В С++ это не так.
А вот стандартное поведение:
http://ideone.com/lNoNx
Видимо, автор думал, что код в деструкторе работает также.
Я понимаю что работать этот код не должен, но почему то работает. Думаю, там ещё какие то проверки и флаги есть, которые всё-таки работают. Я в тот исходник смотрел только 2 минуты, но больше смотреть не хочу. Не моё это дело переписывать чужой работающий говнокод.
На территории конструктора и деструктора класса 'A' (т.е. непосредственно внутри реализации 'A::A' и 'A::~A') выражение 'typeid(*this)' всегда возвращает строго 'typeid(A)', незавивисимо от того, какой тип имеет "полный" конструируемый или уничтожаемый объект.
По этой причине в данном примере условие под 'if' - тавтология. Оно всегда истинно. Поэтому соврешенно не понятно, во-первых, что пытались сделать, и, во-вторых, чего достигли. Данный 'if' - бессмысленен и ничего не меняет.
Никакой борьбы с pure virtual function call таким способом получить не удастся.
if(typeid(*this)==typeid(TDynamicRespons e))
избежать 7ми кратного вызова flush
Чтобы он соответствовал typetag, надо делать его виртуальным.
В деструкторе базового класса в С++ вызывать flush нельзя, тк класс потомок уже деструктирован и вызов будет некорректный. Вызовется flush базового класса или pure virtual call method stub, если метод flush базового класса не имеет реализации.
Чё? Все деструкторы пустые, кроме базового, они ниего не будут делать, деструктор базового вызовется как раз вовремя.
Вообще, любая попытка вызвать любую виртуальную функцию из конструктора или деструктора - говнокод по определению. Это же крестоосновы.
но всегда можно выстрелить себе в ногу и без помощи ТВМ - сохранить в конструкторе derived адрес метода в поле base::my_virtual_flush_ (типа = boost::bind(derived::flush, this)) и его вызывать в base::~base()
только это всё равно будет мегаговнокостыль
потому что derived::flush() наверняка захочет поработать с какими-нибудь членами derived, а они уже давно разрушены, потому то из коробки виртуальные функции в конструкторах и деструкторах в С++ работают как работают
В общем, сдаётся мне, что наследование и РАИИ несовместимы
А 'boost'... Неужели 'boost' предоставляет возможности для того, чтобы подавить это поведение, т.е. привязать указатель непосредственно к конкретной виртуальной функции еще на этапе инициализации из исключить ТВМ из рассмотрения в момент вызова?
ЗА ВИРАТУЛА!!!
Ахаха я знаю как назову следующего виртуала
биндить виртуальную функцию - попадать в ТВМ
ну значит решение выстреливания себе в ногу - для каждой виртуальной inherited::flush() нужна невиртуальная inherited::do_flush(), делающая то, что нужно
в невиртуальной do_flush(), понятное дело, нельзя другие виртуальные вызовы
http://ideone.com/hm5Ve
Виртуальность в конструкторе/деструкторе прокрасно работает, но лишь до уровня конструируемого/деструктируемого класса. Логика этого поведения - общеизвестна.
Помня об этом, можно спокойно вызывать виртуальные функции из конструктора/деструктора.
Да, иногда такие вызовы работают и делают именно то, что задумано. Но по сути меньшим говнокодом от того не становятся. Я бы вообще на это дело варнинги компиляторам добавлял, благо их и так хватает неоднозначных.
А высший пилотаж - это когда конструктор или деструктор вызывает обычную функцию, та - ещё десяток обычных, а какая-то из них на десятом уровне вложенности добирается до виртуальной... это уже никакой компилятор сам не найдёт :(
Но как только мы начинаем рассматривать "опосредованные" вызовы, ситуация меняется. Виртуальные механизмы во время конструкции/деструкции продолжают работать так же, как и работали. Меняется только динамический тип объекта. А виртуальность работает по-старому в рамках "нового" динамического типа.
Например, пусть у нас есть три класса
В данном примере вызов 'foo' внутри 'A::bar' выполнятеся виртуально. Но при вызове 'A::bar' из деструктора 'B::~B' виртуальность ограничена глубиной класса 'B'.
Пока мне не встретится реальный (а не искусственный) пример, когда такое действительно необходимо и при этом правильно и прозрачно выглядит и работает во всех обстоятельствах - продолжаю считать подобные вызовы ошибками (потенциально очень опасными). Встречаются они, к сожалению, совсем не редко, т.к. следить за всеми вызовами нереально, а обнаруживаются обычно при очередном крэше.
В C++ вообще много чего можно делать, но не всё - нужно.
Варианта два: либо выносить всю логику flush() в предка, либо вызывать flush() в дектрукторе каждого наследника.