- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
// A registrator class.
// To register a Parents <- Child relation user must derive their type from
// the correct specialization of this class
template<typename Child, typename... Parents>
struct registrator {
private:
// The registerRelation() function would be called during dynamic initialization of this
// static storage duration variable.
static volatile inline detail::registratorType _registrator
= RelationManager::registerRelation<Child, Parents...>();
protected:
/*
[basic.start.dynamic]/6:
It is implementation-defined whether the dynamic initialization of a non-block inline variable
with static storage duration is sequenced before the first statement of main or is deferred.
If it is deferred, it strongly happens before any non-initialization odr-use of that variable.
[basic.start.dynamic]/4:
A non-initialization odr-use is an odr-use ([basic.def.odr]) not caused directly or indirectly
by the initialization of a non-block static or thread storage duration variable.
By odr-using the _registrator here we are making sure that any Child constructor is odr-using it as well,
thus guaranteeing that the compiler would call registerRelation() strongly before the Child constructor
*/
constexpr registrator() noexcept
{
// Taking an address of a variable, even in a discarded statement, is an odr-use
(void)&_registrator;
}
private:
/*
If registrator() constructor is never 'non-initialization odr-used'
(no Child objects are created besides non-local static ones), then the only
instantiation of the registrator class is an implicit instantiation caused by deriving Child;
[temp.inst]/3:
The implicit instantiation of a class template specialization causes
(3.1) -- the implicit instantiation of the declarations, but not of the definitions, of
the non-deleted class member functions, member classes, scoped member enumerations,
static data members, member templates, and friends; and
(3.2) -- the implicit instantiation of the definitions of deleted member functions,
unscoped member enumerations, and member anonymous unions.
[basic.def.odr]/8:
[...] A constructor for a class is odr-used as specified in [dcl.init].
(In other words, "A constructor (including default constructors) for a class
is odr-used by the initialization that selects it.")
The problem is that if there is no Child() constructor calls, then the compiler is not required to
even DEFINE our registrator() constructor, and since there is only one odr-use of the static inline
variable _registrator, the compiler is not required to generate a definition for it as well. And since
it is not generating a definition, the initialization is also not generated.
But [temp.inst]/3.2 requires that any implicit instantiation of a class template also
causes the DEFINITION instantiation of an unscoped member enumerations. Next,
[dcl.spec.auto.general]/12:
Return type deduction for a templated entity that is a function or function template
with a placeholder in its declared type occurs when the definition is instantiated
even if the function body contains a return statement with a non-type-dependent operand
Thus, using sizeof(*_force_registrator_instantiation()) causes the implicit instantiation
of definition of _force_registrator_instantiation(), and since this function is odr-using _registrator,
its definition (and initialization) is also generated.
The only problem is that by [basic.start.dynamic]/6 the implementation MAY defer the dynamic
initialization of _registrator to the point where it is 'non-initialization odr-used', and since without
the Child() constructor call we don't have any way to non-initialization odr-use it, we also have no way to
make sure that the compiler invokes registerRelation() before main() or before any other point of execution.
The good news, though, is that all three modern compilers obediently initializes _registrator before the first
statement of main().
*/
static auto _force_registrator_instantiation()
{
return &_registrator;
}
enum {
_ROTARTSIGER_ETAITNATSNI = sizeof(*_force_registrator_instantiation()),
};
};
bormand 23.03.2021 22:30 # +2
В итоге так и не получилось по стандарту это запинать, работает только из-за того, что ни один конпелятор пока не заморочился с ленивой инициализацией глобалок?
MAKAKA 23.03.2021 23:10 # 0
причем они называют это "The good news", лол
Soul_re@ver 23.03.2021 23:14 # +4
bormand 23.03.2021 23:24 # 0
И никакие хаки тут не помогут. Ну кроме какой-нибудь конпеляторозависимой хуйни в духе __attribute__((used)), пожалуй.
bormand 24.03.2021 01:09 # +2
PolinaAksenova 24.03.2021 01:33 # +1
bormand 24.03.2021 01:39 # 0
1) Вынес реализацию RelationManager'а в отдельный TU, чтобы его статики не тащили за собой всякое говно.
2) Перестал инклудить потомков registrator'а в main() чтобы их static inline'ы не затянуло в главный TU.
3) Заинклудил потомков registrator'а в TU где нет глобальных статиков, которые я odr-use.
Теперь gcc выбрасывает их и не регает. Если в TU из пункта 3 добавить какую-то пустую сишную функцию и позвать её, то она затягивает за собой static inline от регистратора (хотя по стандарту вроде не обязана, он же inline?) и всё начинает работать.
bormand 24.03.2021 01:47 # 0
Без разбиения на либы регистрирует ;(
Soul_re@ver 24.03.2021 01:48 # +3
bormand 24.03.2021 01:59 # +1
Мне просто хотелось доказать, что в интернете кто-то не прав.
Fike 24.03.2021 02:45 # +2
PolinaAksenova 24.03.2021 01:48 # 0
Кстати, попробуй объявить в TU из пункта 3 публичный (не static) фабричный метод для потомка registrator<> и проверить, произойдет ли регистрация (без вызовов этого метода).
bormand 24.03.2021 01:51 # 0
If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized.
Я odr-use сишную функцию, она не инлайн, поэтому она статики обязана инициализировать. А static inline она, скорее всего, ради пирфоманса инициализирует в этот момент заодно с обычными, как ты и писала ниже.
PolinaAksenova 24.03.2021 01:53 # 0
bormand 24.03.2021 01:57 # +1
З.Ы. Я эту сишную функцию даже не зову, просто адрес вывожу.
bormand 24.03.2021 01:55 # 0
Фиг.
PolinaAksenova 24.03.2021 02:08 # 0
bormand 24.03.2021 02:12 # 0
Ну т.е. объект я реально создаю если он зарегается в фабрике. Я даже метод у него потом зову.
PolinaAksenova 24.03.2021 02:17 # 0
Попробуй в TU-3 определить обычную глобальную функцию вида
И нигде ее не использовать.
bormand 24.03.2021 02:22 # +2
PolinaAksenova 24.03.2021 03:05 # +2
A_definition.cpp:
main.cpp:
Компиляция:
Вывод:
PolinaAksenova 24.03.2021 03:06 # +2
PolinaAksenova 24.03.2021 03:15 # +2
bormand 24.03.2021 03:16 # +1
Наверное нет, т.к. конпелятор просто отложил иняциализацию в бесконечность. А со стороны main'а у тебя нету happens before на этот кусок графа инициализации. И ты ня можешь ожидать, что иняциализация отработала. И что ня отработала тоже не можешь.
PolinaAksenova 24.03.2021 03:30 # +1
Это соответствует стандарту, поскольку, согласно [temp.inst]/4, для генерации инициализации static data member registrator<A>::_registered требуется только упоминание _registered в контексте, требующем его определения (что достигается через тот самый enum).
С другой стороны, когда мы подключаем A_definition как статическую библиотеку, наша программа по сути состоит только из одного translation unit: main.cpp, который ни про какие registrator<A> и знать не знает, так что все выглядит законно. Для точного ответа, конечно, нужно внимательно прорыть стандарт в местах, описывающих линковку (поиск внешних символов).
bormand 24.03.2021 03:25 # +1
Инициализация хоть и форсится, но она никак не соотносится по времени с остальным кодом. Может раньше, может позже, может никогда.
И чтобы получить happens before понадобится цепочка odr use от main. А это по сути ручное перечисление всех классов.
Я прав?
PolinaAksenova 24.03.2021 03:34 # 0
Но да, гарантированно регистрация произойдет только перед тем, когда где-то в цепочке odr use от main произойдет явный вызов конструктора регистрируемого класса.
PolinaAksenova 24.03.2021 03:39 # +1
bormand 24.03.2021 03:59 # 0
Я просто хотел слепить из этого чистую фабрику. Чтобы по какому-нибудь айдишнику создавать объекты. И случай с либами вполне встречается на практике. Но походу для этой цели данный код не подойдет.
PolinaAksenova 24.03.2021 04:07 # 0
Но, в принципе, на практике (*в отличие от UB, для ID можно рассматривать и практический аспект) deferred initialization никто не делает, потому что иначе придется в каждую экспортируемую функцию, использующую зарегистрированные классы, добавлять гвард, что катастрофично ухудшит производительность.
С библиотеками да, проблема: если единственная связь между библиотекой и компилируемым TU – это код инициализации регистратора, то линкер эту библиотеку просто выкинет. Хотя, в принципе, если вместо заглушки сделать реальную регистрацию, то что-то может и получиться.
bormand 24.03.2021 04:12 # 0
А единственная связь через регистратор -- это же как раз цель разбиения на либы, чтобы основной код не думал чего там залинковали/подгрузили, а просто юзал интерфейс.
Ну __atttibute__((used)) работает. Так что практическое решение есть, можно даже в стандарт сильно не вчитываться...
bormand 24.03.2021 04:12 # 0
Виндовые dll'ки с delayed load тоже.
PolinaAksenova 24.03.2021 04:29 # +2
bormand 24.03.2021 08:50 # 0
Ну кстати нет, по крайней мере у gcc не задвоились экземпляры, каждый static inline один раз проинициализировался и с обоих сторон виден одинаково.
bormand 24.03.2021 09:01 # 0
PolinaAksenova 24.03.2021 02:03 # +1
Soul_re@ver 23.03.2021 23:07 # +3
Никакого геморроя с порядком инициализации, никакого гадания "а когда оно вызовется".
MAKAKA 24.03.2021 00:00 # 0
Потому целого класса крестолулзов в сишке нет
bormand 24.03.2021 00:02 # +2
Зато в няшной gcc есть __attribute__((constructor)), который заставляет функцию отработать перед main'ом. И через него подобные фабрики прекрасно создаются. В студии, емнип, тоже можно изъебнуться через какую-то прагму.
И да, именно перед main, а не перед первым использованием как в крестах.
MAKAKA 24.03.2021 00:08 # +1
А в крестах же это совершенно невинно выглядит
а у пети в конструкторе черти ебуца.
bormand 24.03.2021 00:14 # 0
Угу. И не работает. Потому что инициализация может быть отложена до первого использования. А его нету.
З.Ы. И себя за волосы вытягивать из болота тут бесполезно. Нужно реальное использование.
MAKAKA 24.03.2021 00:17 # 0
Потому наверное правильнее всегда делать функцию со статической переменной внутри, и явно ее вызывать.
Так хотя-бы ты будешь понимать когда ты её вызвал, и когда (соответственно) дернулся код
bormand 24.03.2021 00:18 # 0
Ну в том и суть, что неявную регистрацию в фабрике запилить невозможно*.
* в рамках стандарта
guest6 24.03.2021 00:20 # 0
PolinaAksenova 24.03.2021 01:11 # +1
Более того, если в программе есть публичная (достижимая из внешних модулей) функция, в которой происходит odr-use конструктора потомка registrator<> (например, фабричный метод), то для компилятора остается единственный способ конформно реализовать отложенную иняциализацию: вставить во все такие функции потокобезопасную проверку и инициализацию всех объектов, инициализация которых отложена (как это происходит со static переменными внутри функций). Однако подобное решение катастрофично отразится на производительности, поэтому вероятность того, что какой-либо из распространенных компиляторов будет это делать, равна примерно 0.71%.
bormand 24.03.2021 01:22 # +1
Да, сорри, я на таком примере с фейковой фабрикой и тестировал...
guest6 24.03.2021 00:19 # +1
я уже вижу код типа
petya.GetName() //результат нам не важен, но важно, чтобы он создался к этому моменту!
bormand 24.03.2021 00:20 # 0
bormand 24.03.2021 00:50 # +4
- main() -- это корень
- функция достижима если её заюзали
- виртуальная функция достижима если заюзать конструктор
- глобалка достижима если заюзали её, сишную функцию, статик или другую глобалку из того же TU
- всё, что нядостижимо от корня, идёт няхуй
PolinaAksenova 24.03.2021 01:20 # +2
Поскольку вызов main() является non-initialization odr-use, а сама main() является non-inline функцией, petya будет инициализирован до вызова main(). Но только если у него в конструкторе или деструкторе имеются сайд-эффекты:
Soul_re@ver 24.03.2021 01:43 # +3