Статический порядок инициализации в классе heirarchy

Недавно я стал мучительно осознавать статический порядок инициализации 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 всегда кажется, что сначала инициализируется, но я не уверен, как ввести шум в порядке инициализации, чтобы увеличить вероятность сбоя, если поведение не определено.

2 ответа

Решение

Обычно небезопасно полагаться на статический порядок инициализации базового класса и производного класса. Нет гарантии, что статическая инициализация 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 в многопоточном контексте. Если несколько потоков вызывают его одновременно, все может произойти.

Мне интересно, хотя правило, что "порядок инициализации не определен в единицах перевода" все еще сохраняется для статических членов родительского класса, которые необходимы статическим членам дочернего класса.

Да, это так.

Единственный способ, которым статические члены данных связаны с иерархиями наследования (или, вообще говоря, их инкапсулирующими классами), заключается в их полностью определенных именах; их определение и инициализация совершенно не знают / не заботятся об этом.

Другие вопросы по тегам