Я недавно стал мучительно осознавать Статическая инициализация заказа Fiasco. Мне интересно, хотя правило, что «порядок инициализации не определен в единицах перевода» все еще сохраняется для статических членов родительского класса, которые необходимы статическим членам дочернего класса.
Например, скажем, у нас есть (исключая, для краткости, все # охранники и включает в себя)
// a.h
class A {
static int count;
static int register_subclass();
};
// a.cpp
int A::count = 0;
int A::register_subclass() {
return count ++;
}
А потом подклассы A
,
// b.h
class B : public A {
static int id;
};
// b.cpp
int B::id = A::register_subclass();
Здесь есть две единицы перевода со статическими объектами в одной, зависящей от статических объектов в другой при инициализации … кажется, что это может быть случай фиаско статического порядка инициализации.
Мой вопрос: это действительно безопасно?
То есть я гарантированно, что нет никаких шансов, что B::id
будет содержать мусор, скопированный с A::count
до того, как последний инициализируется? Из моих собственных тестов, A
всегда кажется, что сначала инициализируется, но я не уверен, как ввести шум в порядке инициализации, чтобы увеличить вероятность сбоя, если поведение не определено.
Обычно небезопасно полагаться на статический порядок инициализации базового класса и производного класса. Нет гарантии, что статическая инициализация A
произойдет раньше B
, Это определение статический порядок инициализации фиаско.
Вы могли бы использовать построить при первом использовании идиома:
// a.h
class A {
private:
static int& count();
protected:
static int register_subclass();
};
// a.cpp
int& A::count() {
static int count = 0;
return count;
}
int A::register_subclass() {
return count()++;
}
// b.h
class B : public A {
public:
static int id;
};
// b.cpp
int B::id = A::register_subclass();
Обновить: Однако, сказав это, Богдан указал в комментариях
согласно [3.6.2] в Стандарте порядок инициализации в этом конкретном примере гарантирован. Это не имеет ничего общего с наследованием, но с тем фактом, что инициализация
A::count
является постоянная инициализация, что гарантированно будет сделано раньше динамическая инициализация, который является то, чтоB::id
использует.
Но если у вас нет полного понимания таких внутрикачеств, я рекомендую вам использовать построить при первом использовании идиома.
И это нормально в этом случае, но будьте осторожны с такими функциями, как A::register_subclass
в многопоточном контексте. Если несколько потоков вызывают его одновременно, все может произойти.
Мне интересно, хотя правило, что «порядок инициализации не определен в единицах перевода» все еще сохраняется для статических членов родительского класса, которые необходимы статическим членам дочернего класса.
Да, это так.
Единственный способ, которым статические члены данных связаны с иерархиями наследования (или, вообще говоря, их инкапсулирующими классами), заключается в их полностью определенных именах; их определение и инициализация совершенно не знают / не заботятся об этом.