Почему встроенная переменная stati c класса шаблона не инициализирована? - PullRequest
4 голосов
/ 22 марта 2020

Минимальный пример и вопрос

В этом примере встроенная переменная-член stati c c2 не шаблонного класса инициализируется при создании экземпляра класса, а переменная-член c1 шаблона класс нет. В чем разница? Почему c1 не инициализируется, если я не заставлю его принять его адрес, а c2 инициализируется безоговорочно?

struct C1 {                        
    C1() { std::cerr << "C1()\n"; }
};                                 


struct C2 {                        
    C2() { std::cerr << "C2()\n"; }
};                                 

template<typename T>               
struct Template {                  
    inline static C1 c1;           
};                                 

struct Nontemplate {                
    inline static C2 c2;           
};                                 

int main() {                       
    Template<int> a;               
    Nontemplate b;                  
    (void)a;                       
    (void)b;                       
}
// Output:
C2()                                  

Контекст и логика

Вот немного контекста к минимальному примеру. У меня есть класс Nontemplate, унаследованный от Template<something>, а конструктор c2 зависит от c1. Я ожидаю, что c1 будет создан до c2; однако это не так.

template<typename T>                                                     
struct Template {                                                        
    inline static C1 c1;                                                 
};                                                                       

struct Nontemplate : public Template<int> {                               
    struct C2 {                                                          
        C2() {                                                           
            std::cerr << "Do something with Nontemplate::C1\n";          
            std::cerr << "&Nontemplate::c1 = " << &Nontemplate::c1 << "\n";
        }                                                                
    };                                                                   
    inline static C2 c2;                                                 
};                                                                       

int main() {                                                             
    Nontemplate b;                                                        
    (void)b;                                                             
}                                                                        
// Output:
Do something with Nontemplate::C1
&Nontemplate::c1 = 0x600ea8       
C1()                             

Код был скомпилирован с g ++ 7.2 с флагами -std=c++17. И -O0, и -O2 дают одинаковый результат.

1 Ответ

4 голосов
/ 22 марта 2020

Неявное создание экземпляра шаблона класса вызывает только создание экземпляров объявлений, которые он содержит. Определения, как правило, создаются только в том случае, если они используются в контексте, для которого требуется определение.

Таким образом, если вы не используете Template<int>::c1 таким образом, чтобы его определение существовало (т.е. с использованием odr), тогда он вообще не будет определен.

Один из способов использования odr с переменной - это взять ее адрес, как вы упомянули.

Даже если вы форсируете В случае создания экземпляра переменной нет гарантии, когда именно она будет инициализирована. Конструктор

C1 не является constexpr, поэтому инициализация Nontemlate::c1 не может быть константным выражением. Это означает, что вы получите динамическую c инициализацию Template<int>::c1. Динамическая c инициализация глобальных переменных stati c, являющихся частью специализации шаблона, неупорядоченная , что означает, что нет никакой гарантии, в каком порядке они произойдут относительно любой другой динамической c инициализации глобальные переменные c.

Точно так же Nontemlate::c2 не инициализируется константным выражением и поэтому также динамически инициализируется. Хотя Nontemlate::c2 имеет частично упорядоченную динамическую c инициализацию (являющуюся переменной inline, не являющейся частью специализации шаблона), она все еще неопределенно упорядочена относительно Template<int>::c1, как объяснено выше.

Также строго не требуется, чтобы Template<int>::c1 и Nontemlate::c2 были инициализированы до ввода main. Это определяется реализацией, будет ли инициализация отложена до более позднего периода, но до первого использования соответствующей переменной. Я думаю, что эта отсрочка используется в основном для динамической загрузки библиотеки c во время выполнения.

Распространенный метод, позволяющий избежать проблем с упорядочением глобальных переменных продолжительности хранения stati c, заключается в использовании функции, возвращающей ссылку на вместо этого локальная переменная stati c, то есть:

template<typename T>               
struct Template {                  
    static auto& c1() {
        static C1 instance; 
        return instance;
    }          
};

, хотя при частых вызовах это может повлиять на производительность, поскольку каждый раз необходимо проверять структуру локального stati c.

Альтернативно, если инициализатор можно сделать постоянным выражением, создание переменной constexpr должно гарантировать постоянную инициализацию , что означает, что никакой инициализации динамически c не произойдет вообще и не возникнет проблем с упорядочением. .

...