4

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

+3

Нет. Порядок подключения a.cpp и b.cpp к исполняемому файлу * не * гарантирован. Это * - «фиаско порядка инициализации». (Ваш линкер может использовать алфавитный порядок, или он может не быть). –

+0

@BoPersson Я боялся этого. Благодарю. Сделать это ответ (и, может быть, предоставить ссылку?), И я пометю его принятым – stett

+0

@BoPersson: Ответы идут туда приятель ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ –

ответ

3

Как правило, небезопасно полагаться на статический порядок инициализации базового класса и производного класса. Нет гарантии, что статическая инициализация A произойдет до B. Это определение static initialization order fiasco.

Вы можете использовать конструкцию при первом использовании идиомы:

// 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(); 

Live demo.

Update: Однако, говоря, что, Bogdan отметил в комментарии

согласно [3.6.2] в Стандарте, порядок инициализации в этом конкретном примере гарантирован. Это не имеет ничего общего с наследованием, но с тем фактом, что инициализация A::count равна константной инициализации, которая гарантируется перед динамической инициализацией, что и используется B::id.

Но если у вас есть полное понимание таких intracaccies я рекомендую вам использовать конструкцию на первом использование идиомы.

И это нормально в этом случае, но будьте осторожны с функциями, такими как A::register_subclass в многопоточном контексте. Если несколько потоков называют это одновременно, все может случиться.

+0

Спасибо - это, по сути, решение, в результате которого я использовал , Слишком плохо, что нужно добавить эту неприятную статическую функцию, крутую вокруг моей бедной простой переменной. – stett

+0

Фактически, согласно [3.6.2] в Стандарте, порядок инициализации в конкретном примере OP гарантирован. Это не имеет ничего общего с наследованием, но с тем фактом, что инициализация 'A :: count' является * постоянной инициализацией *, что гарантировано сделать до * динамической инициализации *, что и используется' B :: id' , 'count ++', однако, может быть проблемой при наличии потоков - инициализация 'B :: id' и некоторого другого' C :: id' потенциально может запускаться одновременно. cc @stett – bogdan

+0

Совет по использованию локальных 'static', как правило, звучит, но я думаю, что, возможно, стоит уточнить приведенные выше детали в ответе в интересах точности. – bogdan

0

Мне интересно, однако, если правило, что «порядок инициализации не определено в единицах перевода» по-прежнему сохраняется для статических членов в родительском классе, которые необходимы статическим членам в дочернем классе.

Да, это так.

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