[C++] деструкторы статических объектов
Полагаю, такой же отстой можно поиметь и с нетривиальными конструкторами...с конструкторами проще, т.к. там рекомендуется делать статический экземпляр создаваемый по первому требованию, а не статический конструктор.
Вообще, афаик, считается стандартной практикой то, что, если твой деструктор делает какие-то нетривиальные вещи, используя какие-то другие объекты - это не подходит для деструктора, и тебе нужен объект с явным временем жизни (см. IDisposable).
Это что еще за статический экземпляр, создаваемый по первому требованию? Есть класс, написанный одним программистом, другой где-нибудь описал глобальную переменную этого типа.
А есть C++, где деструктор — это единственный способ задать явно время жизни объекта и отдать гарантию выполнения этого требования на откуп компилятору.
Но это ничего не меняет; мне кажется, что даже в плюсах разработчик не должен хотеть делать что-то сложное в деструкторе долгоживущего объекта.
Если у тебя есть функция, явно работающая с глобальными данными, то надо понимать, что позвать ее могут откуда угодно (она же глобальная а поэтому обеспечить соответствующим глобальным объектам наиболее объемлющее время жизни: убрать их в общий файл, которому поставить приоритет инициализации (конкретный способ, естественно, зависит от компилятора, можешь погуглить).
А вообще да, деструктор C++ и финализатор .NET — это вещи, имеющие очень мало общего.
человек спрашивает, как проектировать класс, когда он не знает, кому как взбредет в голову его использовать.Можно подумать, что объект этого класса сам по себе где-то существует синглтоном, пока кто-то внезапно не позовёт у него деструктор.
Есть класс, написанный одним программистом, другой где-нибудь описал глобальную переменную этого типа.и вот экземпляр этой глобальной переменной должен создаваться по первому требованию.
В случае с циклическими зависимостями это не поможет. Думать всё равно надо.
В случае с циклическими зависимостями это не поможет. Думать всё равно надо.с циклами вообще ничего не поможет, кроме их развязывания
но в исходном посте - проблема не с циклами.
Единственная рекомендация на сей счет, которую я встречал, состоит в избегании глобальных переменных.Можно заменить глобальные переменные на указатели, и тогда у тебя будет контроль над тем, что происходит. А вообще проблема достаточно стандартная, часто происходит в сторонних С++ on C библиотеках типа Qt.
с циклами вообще ничего не поможет, кроме их развязыванияНадо изначально писать так, чтобы было чётко видно, в каком порядке создаются/уничтожаются такие долгоживущие "глобальные" объекты.
но в исходном посте - проблема не с циклами."Правильная" борьба с циклами решит и проблему в исходном посте.
"Правильная" борьба с циклами решит и проблему в исходном посте.вопрос был: как не создавать подобных проблем, требовались простые четкие правила.
и вот экземпляр этой глобальной переменной должен создаваться по первому требованию.что значит «должен»? Как программист может на это повлиять?
Что значит «требование»? Вот если я напишу
когда надо создавать x?
class A;
A x;
int y = reinterpret_cast<int>( &x ) + 18;
что значит «должен»? Как программист может на это повлиять?
Повлиять можно, синглетоном если класс сделать.
Это обходной путь. Если куча GSO уже есть проблемы такой не избежать.
Если уже есть куча, то они как то создавалисмь раньше, а новых в любом случае лучше не добавлять.
Придется выдумывать им имена и объяснять другим программистам, что это вот так у нас забацаны глобальные переменные.
Это еще и не решит проблемы с деструкторами, но будет вырвиглазная кривота. Ты видел такое в коммерческом коде? Я — нет, и проблемы с порядком инициализации решены. Нормальный подход к решению я описал выше.
Интересно, а зачем может понадобиться много глобальных переменных.
Придется выдумывать им имена и объяснять другим программистам, что это вот так у нас забацаны глобальные переменные.
Это еще и не решит проблемы с деструкторами, но будет вырвиглазная кривота. Ты видел такое в коммерческом коде? Я — нет, и проблемы с порядком инициализации решены. Нормальный подход к решению я описал выше.
А переменным имена не надо придумывать?
Чего то я не понял, ты считаешь, что лучше использовать глобальные пременные, чем синглетоны, да еще и в комерческом проекте?
мне вот почему-то кажется, что когда есть несколько сложных глобальных переменных, их нужно завернуть в один класс и сделать одну глобальную переменную - экземпляр этого класса.
вопрос был: как не создавать подобных проблем, требовались простые четкие правила.Простые чёткие правила - надо явно управлять жизнью таких "долгих" объектов в одном месте; а когда это будет сделано - в этом самом месте сделать так, чтобы нужный объект финализировался не позже используемого его финализатором.
да еще и в комерческом проекте?а тебя так пугает это сочетание слов? Мы синглтонией не страдаем
ну а если серьезно, то ты так и не понял вопрос. По крайней мере в полном объеме. Речь идет о правилах проектирования класса, а не стратегии использования глобальных переменных. Т.е. ищутся правила (как можно более слабые) написания классов, глобальные статические экземпляры которых можно безопасно создавать.
Попробуй прочитать мой пост и понять, что "финализатором" я просто называю некоторый явно вызываемый метод. Мог бы назвать горшком, а назвал финализатором.
Это не C++-way.
Меня удивляет, что вы используете большое количество глобальных переменных в крупном проекте.
Это не C++-way.C++-way - писать плохо?
Мимо (это я тебе).
Я ничего не говорил про большое, я говорил про то, что мы не пугаемся глобальных константных строк, которые являются довольно хитрыми объектами, зависящими от глобальных данных. И не оборачиваем их в синглтоны. Еще есть некоторые специальные глобальные классы, к которым по сути прямого обращения и нет (не вдаюсь тут в детали). В общем мы знаем что делаем, а не боимся языка
Надеюсь константные глобальные строки зависят от константных глобальных данных?
А то как то странно получтся.
Знаешь, что такое аллокаторы памяти в C++?
1) можешь убрать из моего поста слово «константные», ибо неконстантные мы тоже используем. И использование это ограничивается не боязнью нарваться на зависимости от порядка инициализации, а из соображений дизайна;
2) с константными объектами все описанные неприятности сохраняются, они ничем не лучше и не хуже;
3) нечего играть словами «константа», «переменная», если экземпляр класса — то сразу переменная, то, что у нее нельзя напрямую вызвать неконстантные методы — это уже другой вопрос.
Меня удивляет, что вы используете большое количество глобальных переменных в крупном проекте.Нет ничего плохого в глобальных переменных, доступных только в одной единице компиляции. Во всяком случае, если это не вызывает проблем с многопоточностью.
По поводу общих глобальных переменных и синглтонов, тут есть три вопроса:
1. синглтон или не синглтон - целиком определяется требуемой функциональностью, если нужно гарантированно не более одного объекта, то используем синглтон, в противном случае - что-то другое;
2. нужно создание по требованию или создание в начале работы - определяется как требованиям к коду, так и ответом на 1й вопрос (синглтон обычно создаётся при первом обращении);
3. использова объект(ы)-обёртку/статический метод класса, либо глобальные переменные для доступа к экземпляру(ам): если в одном из первых вопросов выбран первый вариант, то решение без глобальной переменной уже, по идее, должно быть, в противном случае не всё так однозначно.
libc/libstdc++? В ядре?
Более того, многие из них являются локальными.
Этот кошмар, по-видимому, должен просто добить.
---
"Quae medicamenta non sanat, ferrum sanat,
quae ferrum non sanat, ignis sanat."
Плохо когда их много.
Да там такой жести и не возникает, насколько я знаю.
Плохо когда их много.совсем глобальных, которые в заголовке, включаемом в большинство файлов данного куска проекта? Тогда вопрос следующий: со скольки начинается много?
Развели тут спор, вы хотите убедить меня, что глобальные объекты делают потому, что это хорошо и красиво, а не тогда, когда по другому сделать нельзя или очень сложно?
> libc — это отдельный объект, который должен сохранять
> состояние между обращениями к нему.
Сомнительно.
Дублирование API (syslog_r, strerror_r, ttyname_r, ctime_r, getpwent_r
и много других "_r") только подтверждает сомнительность твоего заявления.
---
Q7: А что за suxx?
A7: unix.
Миллион точно плохоЧем плох g_my_favourite_class_instance[1000000]?
потому, что это хорошо и красиво, а не тогда, когда по другому сделать нельзя или очень сложно?Какой-нить g_my_class_instance явно лучшее чем CMyClass::GetInstance если классов не слишком много. Просто лишние два двоеточия и скобочки не всегда себя оправдывают.
Чем плох g_my_favourite_class_instance[1000000]?
Тем что памяти отожрет 1000000*sizeof(g_my_favourite_class_instance)
и вызовет столько же конструкторов в момент старта приложения.
Какой-нить g_my_class_instance явно лучшее чем CMyClass::GetInstance если классов не слишком много. Просто лишние два двоеточия и скобочки не всегда себя оправдывают.
Синглетон нужен когда, надо отсрочить инициализацию, или есть вероятность, что объект никогда не понадобится, а не для красивой записи, т.к. по сути это то же глобальный объект.
Мне кажется, вы неправильно поняли, я написал что синглетон можно использовать, для того что бы влиять на время создания глобального объекта, и это не имеет никакого отношения, к тому, что я считаю, что большое количество глобальных объектов не есть хорошо, т.к. синглетон тоже глобальный объект.
Синглетон нужен когда, надо отсрочить инициализацию, или есть вероятность, что объект никогда не понадобится, а не для красивой записи, т.к. по сути это то же глобальный объект.Я рассматриваю вариант, когда синглтон в принципе не требуется.
Тем что памяти отожрет 1000000*sizeof(g_my_favourite_class_instance)памяти отожрёт ровно 1*sizeof(g_my_favourite_class_instance а если вдруг смысл будет в самих конструкторах и деструкторах, а не в данных, то sizeof(g_my_favourite_class_instance[0]) вовсе может оказаться равным нулю Короче, всё зависит от задачи и пока мы не рассматриваем какие-то конкретные примеры, спор будет бессмысленным.
и вызовет столько же конструкторов в момент старта приложения.
Мне кажется, вы неправильно поняли, я написал что синглетон можно использовать, для того что бы влиять на время создания глобального объекта, и это не имеет никакого отношения, к тому, что я считаю, что большое количество глобальных объектов не есть хорошо, т.к. синглетон тоже глобальный объект.ну я вроде ещё раньше расписал, что в этом обсуждении смешаны три проблемы выбора (единственность экземпляра, время создания и использование глобальной переменной или какой-либо обёртки две из которых не относятся ко вкусам, а выбор в третьем случае зависит от выбора в первых двух.
Ну разве не плохо, когда у тебя нет выбора и приходится создавать кучу глобальных объектов
Кстати, не видел этих функций, более того, на моей состеме find /usr/include/ -iname \*.h -print0|xargs -0 grep -H syslog_r ничего не находит.
Ну разве не плохо, когда у тебя нет выбора и приходится создавать кучу глобальных объектоввыбор есть почти всегда. Тем более что случаев, когда глобальные объекты являются вполне приемлемым решением, конфигурационно меньше (хотя, статистически именно такой вариант может чаще встречаться в небольших проектах или при определённом стиле).
> когда создавали эти crt и libc, но о реентерабельности-то
> можно было догадаться.
Во времена Unix v.7 не задумывались об этом.
> Кстати, не видел этих функций, более того, на моей состеме
> find /usr/include/ -iname \*.h -print0|xargs -0 grep -H syslog_r
> ничего не находит.
HISTORY
These non-multithread-safe functions appeared in 4.2BSD. The multi-
thread-safe functions appeared in OpenBSD 3.1 and then in NetBSD 4.0.
The async-signal-safe functions appeared in NetBSD 4.0. The syslog-pro-
tocol functions appeared in NetBSD 5.0.
---
A44: Ламеры в гамаке пусть в тапках трахаются --- это их проблемы.
Я в своём гамаке хочу полноценно трахаться на лыжах.
что значит «должен»? Как программист может на это повлиять?очень и очень странно.
Что значит «требование»?когда надо создавать x?
дожили до таких седин, а пугаетесь слова синглетон, и не знаете как синглетон дешево делается на C++
описание static переменной
A a;
можно заменить на такой код
inline A& a
{
static A _a;
return _a;
}
или такой код
inline A& a
{
static A * _a = new A;
return *_a;
}
и переменная a будет создаваться по первому требованию(при первом использовании а не при старте программы.
и переменная a будет создаваться по первому требованию(при первом использовании а не при старте программыОй, а как там всё интересно, когда a вызывается сразу из нескольких потоков...
Но подобного я и правда нигде не видел. Точно не уверен, как к этому отнесутся коллеги =)
Второй пример, правда, не понял, зачем утечку памяти создавать.там нет утечки памяти, т.к. определение утечки памяти гласит - что память выделена и переменных ссылающихся на этих память уже не осталось.
зато память потребляется только если действительно переменная использовалась, в первом примере - память отъедается сразу (но инициализируется по первому использованию)
Ок, но то, что не вызван деструктор, уже плохо.
Ой, а как там всё интересно, когда a вызывается сразу из нескольких потоков...
T& GetInst
{
static T t;
return t;
}
The current version of the standard completely ignores the existence of
threads (and almost completely ignores the existence of dynamic
libraries). Therefore, the code is not thread safe. But you can use
environment-specific means to make e.g. the initialization of the static
variable thread safe. One simple way is to call GetInst when only the
main thread has started, no others yet started. Another way, less
efficient, to ensure mutual exclusion for calls to GetInst. A third
way, if it's OK to let each thread have its own T object, to use thread
local storage. Boost threads support all three solutions. Of course,
the first one doesn't require any special support, so the support
consists of nothing, but by definition it's there...
One simple way is to call GetInst when only the main thread has started == глобальная переменная.
Another way, less efficient, to ensure mutual exclusion for calls to GetInst == лочим мьютекс на каждый вызов функции.
A third way, if it's OK to let each thread have its own T object, to use thread local storage == не синглтон.
Так это все не методы сделать thread-safe синглетон. Это воркараунды на случай если очень надо.
Помнится, я когда-то уже примерный код шаблонного класса, при добавлении которого в родители класс становится синглтоном. При доведении этого дело до потокобезопасной реализации, получится, имхо, более понятная вещь, нежели стандартный для синглтонов вариант. Впрочем, я сейчас подумал, что если объединить оба варианта, получится ещё один бонус: экземпляр не будет занимать место в куче.
А почему не упомянуть реализацию синглтона с блокировкой и двумя проверками?А смысл? Тредобезопасности это не добавляет.
А смысл? Тредобезопасности это не добавляет.при правильной реализации и не убавит, зато добавит скорости (блокировки будут вызваться только примерно в момент создания экземпляра).
по-моему, гуманнее заранее в архитектуре предусмотреть инициализацию всех необходимых синглтонов однотредно, нежели долго и кропотливо отлаживать синглтон на принципе Double check-а и бояться, что от смены компилятора или на другой платформе всё снова развалится
One simple way is to call GetInst when only the main thread has started == глобальная переменная.== глобальная переменная, но без проблем с неопределенностью порядка инициализации в разных единицах трансляции.
)
так что это имхо все-же чуть лучше, чем "глобальная переменная" для сложных a
Все-таки меня не покидает мысль, что это гнилое решение этой проблемы и синглтон притянут за уши.
== глобальная переменная, но без проблем с неопределенностью порядка инициализации в разных единицах трансляции.Для этого достаточно более простого решения: объявляем указатель. И ещё с указателем можно будет проверять утечки памяти какими-нибудь стандартными средствами.
О, и кстати, по поводу "чтения книжек". Маерс очень пугал своих читателей тем, что если вдруг, ну мало ли, вызов этой хитрой функции a не встроится, то есть шансы получить вообще красивое поведение: по копии A на единицу трансляции. От этого нынче не все компиляторы страдают, да, но все же, это плохое решение еще и поэтому.
Под g++ это падает, "recursive init". Под студией работает, но объекты удаляются в неправильном порядке.
struct A {
A { printf( "A created\n" ); f; }
~A { printf( "A destroyed\n" ); }
void f;
};
struct B {
B { printf( "B created\n" ); f; }
~B { printf( "B destroyed\n" ); }
void f;
};
A& GetA
{
static A _a;
return _a;
}
B& GetB
{
static B _b;
return _b;
}
void A::f { printf( "A::f\n" ); GetB; }
void B::f { printf( "B::f\n" ); GetA; }
int main
{
GetA;
return 0;
}
На самом деле Майерс так писал? Бред же полный.
> От этого нынче не все компиляторы страдают
Очень интересно. Какие страдают? Какие страдали в прошлом?
Бред же полный.ниче не бред. Пока не настала стадия линковки, так и есть, потому что входные файлы обрабатываются независимо.
Но вообще это я в качестве шутки написал. Просто не люблю писать inline, не считаю себя умнее компилятора в вопросах оптимизации.
Очень интересно. Какие страдают? Какие страдали в прошлом?можешь узнать, если интересно
до
{
static A _a;
return _a;
}
когда у тебя еще конструктор А от первого вызова не отработал
в реальной жизни такое, я думаю, полюбас приведет к проблемам, даже если бы тут возвращался еще не полностью сконструированный объект (как делает студия, я подозреваю)
В большинстве компиляторов порядок деинициализации объектов - обратный порядку инициализации. Соответвенно достаточно выстроить порядок инициализации - а его довольно легко сделать правильным, например обернув тот статический объект,который должен инициализироваться первым внутрь функции, и делая доступ к объекту через эту функцию.
static CMyClass& GetMyStaticObject
{
static CMyClass myStaticObject;
return myStaticObject;
}
Т.е. в твоем примере я бы попробовал в конструкторе A дернуть фукцию, обертывающую B - тогда B создастся раньше и деинициализируется позже.
Пока не настала стадия линковки, так и есть, потому что входные файлы обрабатываются независимо.И при чём тут инлайнинг? Сколько будет копий А на единицу трансляции, если a заинлайнится?
Я пока не вижу логики в твоих словах.
Похоже, что какой-то компилятор при невозможности заинлайнить просто заменял inline на static. Но это криминал, да
Все-таки меня не покидает мысль, что это гнилое решение этой проблемы и синглтон притянут за уши.Но Маерс в More Effective C++ Item 47 рекомендует поступать именно так
издания).
Мда, рассуждения действительно странные, но Маерс пишет об этом в той же книге More Effective C++ в Item 33 (страница 133 русского Краткий вывод такой: если есть зависимость между синглтонами, то одновременно просто и хорошо не будет по любому. Однако есть разные варианты со своими плюсами и минусами
Может, тебе какой и поможет. Например, B надо делать или
а) "фениксом" (если удалили, но потом опять обратились, то пусть создается)
или
б) определять целочисленные индекс, ранжирующий жизнь объекта (чтобы гарантировать, что синглтон переживет все объекты с меньшим индексом и тогда проектировать так, чтобы "младшие" синглтоны в своей реализации не зависели от старших.
(Ну, разумеется буквально также кодировать, как он предлагает, или использовать библиотеку Loki я не предлагаю, но полезные идеи оттуда извлечь можно.
Ну и это все при условии, что мы оставили в стороне разговоры вроде, "синглтон - это глобальная переменная в овечьей шкуре" и его использовать нельзя наравне с goto
! 5/15/96 sdm 134 Add footnote: 9/26/96
In July 1996, the ISO/ANSI standardization
committee changed the default linkage of inline
functions to external, so the problem I
describe here has been eliminated, at least on
paper. Your compilers may not yet be in accord
with the emerging standard, however, so your
best bet is still to shy away from inline
functions with static data.
6/22/98 sdm xiv Borland is now Inprise. 6/27/98
...
1/28/08 sdm xiv Borland is no longer Inprise. 1/28/08
Раз уж тут о синглетонах речь пошла, присоединяюсь в вопросу. Как все же быть с вызовом деструктора A во втором примере?
А зачем вообще писать так, как во втором примере?
Кстати, я так понял, что такой способ позволит программе не падать. Положим, сначала вызвались деструкторы в данном модуле трансляции, а теперь работает деструктор в другом модуле и вызывает эту функцию a. Думаю, нам никто не обещал, что мы можем помацать A*, но в реальности с самим указателем вряд ли что успело произойти, что его прочитать нельзя, правильно?
И теперь эта функция a лежит в dll, которая во время работы сервера туда-сюда 10 загружается и выгружается. Потечет?
но в реальности с самим указателем вряд ли что успело произойти, что его прочитать нельзя, правильно?"в реальности" бывают разные вещи. Например, A мог быть выделен на каком-нибудь странном аллокаторе, который уже удалеееен...
Потечет?Если допустить, что разговор о винде, то если A выделен на куче процесса (тот же malloc, через который по умолчанию работает new, в реализации CRT by Microsoft выделяет именно там то точно потечет.
И теперь эта функция a лежит в dll, которая во время работы сервера туда-сюда 10 загружается и выгружается. Потечет?для каких практических задач используется выгрузка dll, с последующей повторной загрузкой?
Кстати, еще детекторы утечек будут ругаться.
У меня пример умозрительный. На практике такое бывает? Если придумаю практический пример, напишу.мне цель этого действия не понятна.
сэкономить память за счет выгрузки dll-ки? такое может быть только, если dll-ек очень и очень много.
например, при генерации dll-ек, но почему тогда код просто в памяти не генерится?
Оставить комментарий
Realist
У меня есть статический объект A, у которого нетривиальный деструктор. Этот диструктор вызывает функцию из другого модуля. В том модуле та функция пользует какой-то свой статический объект B. Ну и по стечению обстоятельств к моменту вызова деструктора А этот второй объект B уже разрушен. Все это падает с треском.Единственная рекомендация на сей счет, которую я встречал, состоит в избегании глобальных переменных. Есть ли еще какие-то практики, или же явные указания, о том, как обезопасить себя?
Например, когда я проектирую класс, я предполагаю, что кто-то когда-нибудь попробует выполнить присваивание и реализую оператор = (запрещаю копирование...).
Должен ли я полагать, что когда-нибудь кто-нибудь захочет создать глобальный объект моего класса? Или я должен всегда реализовывать деструктор так, чтоб он не вызывал функций за пределами данного файла? Должен ли я, когда пишу функции, подумать о том, что они используют статические переменные? Могу ли я в деструкторе вызывать стандартные функции?
Полагаю, такой же отстой можно поиметь и с нетривиальными конструкторами...