[C++] правила проектирования (было: деструкторы статических объектов)

Realist

Поскольку в соседней ветке больно много ответов и большинство их них не на тот вопрос, я бы хотел конкретизировать.
Первый программист пишет библиотеку и предоставляет клевый класс А.
  
//libraryA.h:

// клевый класс
class A {...};

Второй программист пишет удобную функцию f:

#include <libraryA.h>

//полезная экспортируемая функция
void f
{
static A a;
....
}

Третий программист использует это дело в своем мега-классе B

class B
{
...
~B { f; }
};

Наконец, четвертый программист берет класс B и делает статический объект

void g
{
static B b;
.....
}

Все это не работает, поскольку к моменту уничтожения B b статический объект A a уже уничтожен. Обратите внимание, что глобальных объектов тут вообще нет. Не надо мне тут про синглетоны. Также прошу заметить, что дело происходит в разных библиотеках и реализации классов и функций вообще могут быть скрыты, библиотеки поставляются в бинарном виде. Вопрос. Кто из программистов и какое правило (принцип) проектирования нарушил?
Если я правильно понял ответы именно на эти вопросы, то вот что получается (пусть авторы меня поправят):
penartur2 полагает, что нефиг из деструктора звать f. Третий программист должен был убедиться, что код у f простой и без подводных камней.
указывает, что нефиг писать static до тех пор, пока не обеспечил нужное время жизни. Второй и четвертый программисты — лопухи.
Mike тоже винит тех, кто засунул под static'и сами объекты, а не на указатели.

Serab

Обратите внимание, что глобальных объектов тут вообще нет. Не надо мне тут про синглетоны.
Ты крут, правда, похоже, что не понял этого :grin:
Твой код показывает, почему синглтоны — не панацея в общем случае :D
Просто надо переименовать f в GetA из примера DarkGray.

Serab

Все-таки остаюсь при своем. Чтобы делать объект статическим, надо быть аккуратным. Просто несколько раз обжигались подобным образом и в более простых случаях. Статический объект должен уметь жить без других, ну либо без тех, о чьем времени жизни нельзя утверждать, что оно заведомо содержит время жизни самого объекта (кстати, это очевидное утверждение имхо так.
Но пенартур тоже прав в некотором роде: если зовешь из деструктора, надо быть аккуратным. Не только из-за глобальности, надо как минимум быть уверенным, что функция исключений не кидает, к примеру.

Serab

Вообще при обсуждении подобных проблем (обычно сразу после того, как потратил на отладку пару часов, ну или пару дней :grin:) возникает желание сказать что-то типа "нахуй C++, лучше сразу не в ногу, а в голову выстрелить и на этот раз в прямом смысле". Успокаивает мысль, что люди и более неразрешимые проблемы решали :D

Serab

Также прошу заметить, что дело происходит в разных библиотеках
О, это тоже момент. В разных библиотеках все проще: библиотечные статические объекты уничтожаются во время FreeLibrary. А если никто FreeLibrary явно не вызывал (а это случай load-time загрузки, т.е. когда линкуешься к "динамической" lib'е то после уничтожения всех статических объектов основного модуля.
Создаются, соответственно, во время LoadLibrary (глобальные объекты либо при вызове соответствующей функции (локальные статические, ну это как обычно). Причем в случае loadtime загрузки создаются до объектов основного модуля.
Это винда, в юниксовом программировании пока слабоват =)
Вот, придумал по дороге с работы, сейчас затестил. "Так и вышло".

zya369

В разных библиотеках все проще: ...
Вот, придумал по дороге с работы, сейчас затестил. "Так и вышло".

я правильно понял, что тебе пришла мысль "как это может быть", ты проверил ее на примере, все сошлось, и теперь ты утверждаешь, что так оно и должно быть всегда? :shocked:

Andbar

Да всем ведь известно, что в DllEntryPoint нельзя делать ничего, что бы закладывалось на доступность других модулей, а значит и к статическим объектам в dll-ке нужно крайне осторожно относиться, не так, как к статическим объектам основного модуля.
Можно, конечно, создать обёртку вокруг механизма подгрузки-выгрузки dll, но для этого нужно править стандартную библиотеку и смысла в этом нет.

Serab

я правильно понял, что тебе пришла мысль "как это может быть", ты проверил ее на примере, все сошлось, и теперь ты утверждаешь, что так оно и должно быть всегда? :shocked:
нет, я подумал о механизме уничтожения статических объектов в CRT, решил, что иходя из них так должно быть всегда. Пришел домой, проверил, так и вышло. Поэтому в винде так будет всегда.

lilia_rass

Ну этот код по крайней мере под gcc нормально работает (или я что-то не так понял):
calling g
starting g
create B
finishing g
finishing main
delete B start
starting f
create A
finishing f
delete B finish
delete A

как видно, А уничтожается в самом конце.
Для того, чтобы его сломать мне пришлось добавить вызов f в g после static B b; т.е.
void g
{
static B b;
f;
}

а тут уже не так:
calling g
starting g
create B
starting f
create A
finishing f
finishing g
finishing main
delete A
delete B start
starting f
finishing f
delete B finish

здесь начали исполнять f когда уже прибили A - нехорошо.
Кстати, по-поводу библиотек, здесь от них у меня поведение не зависело, хотя я пробовал линковаться как статически, так и динамически.
Я так понимаю, поскольку заранее нельзя узнать, нужно ли вообще вызывать деструкторы для статических объектов, то их порядок определяется в рантайме, судя по порядку вызова конструкторов?

Serab

(или я что-то не так понял):
вызови f в main после создания B.

Serab

Я так понимаю, поскольку заранее нельзя узнать, нужно ли вообще вызывать деструкторы для статических объектов, то их порядок определяется в рантайме, судя по порядку вызова конструкторов?
это да, но между библиотеками общий подрядок по крайней мере ms-компилятор поддерживать не пытается.

lilia_rass

Не помогает:
starting f
create A
finishing f
calling g
starting g
create B
finishing g
finishing main
delete B start
starting f
finishing f
delete B finish
delete A

Как видно, A прибили также в самом конце.

Serab

я там обновил пост :crazy:

lilia_rass

Ок, если в main идёт g; f; то плохо, да. Но это по сути то же, что в g вызвать f :)

Serab

конечно нет =)
Там гарантированный пиздец, а тут — как повезет (вызовут g где-нибудь или нет). Прикинь, у тебя программа, и она падает в конце работы только если ты вызваешь в каком-то месте какую-то функцию, иначе работает прекрасно. И даже если падает, то до падения работает прекрасно.
Оставить комментарий
Имя или ник:
Комментарий: