Константные переменные в файле заголовка и фиаско статической инициализации
После прочтения многих вопросов, касающихся инициализации статических переменных, я все еще не уверен, как это относится к переменным const
на уровне пространства имен.
У меня есть следующий код в файле заголовка config.h
, сгенерированном конструкцией script:
static const std::string path1 = "/xyz/abc";
static const std::string path2 = "/etc";
В соответствии с тем, что я прочитал, ключевое слово static
не требуется, даже не рекомендуется использовать его.
Мой вопрос: Является ли код выше склонным к статическому фиаско инициализации?
Если в файле заголовка myclass.h
есть следующее:
class MyClass
{
public:
MyClass(const std::string& str) : m_str(str) {}
std::string Get() const { return m_str; }
private:
std::string m_str;
}
const MyClass myclass1("test");
Будет ли это представлять проблемы со статической инициализацией?
Если я правильно понял, из-за переменных const
, имеющих внутреннюю связь, в обоих случаях не должно быть проблем?
Изменить: (из-за ответа dribeas)
Возможно, я должен упомянуть, что меня интересуют такие случаи использования, как:
В main.cpp
:
#include <config.h>
#include <myclass.h>
std::string anotherString(path1 + myclass1.Get());
int main()
{
...
}
Еще один вопрос, связанный с этим прецедентом: будет ли в этом случае оптимизировать компилятор path2
?
Ответы
Ответ 1
Я попытался получить необходимую информацию прямо из стандартного документа С++ 03. Вот что я нашел:
Относительно объявлений const static
:
В соответствии с разделом 3.5.3 объекты, определенные на уровне пространства имен и объявленные const
, по умолчанию имеют внутреннюю привязку. static
также объявляет объект уровня пространства имен внутренней связью, поэтому нет необходимости объявлять объект static const
.
Также согласно Приложению D.2
Использование ключевого слова static устарел при объявлении объектов в пространство имен (см. раздел 3.3.5).
Что касается фиаско статической инициализации:
Поскольку переменные определены в заголовочном файле, они всегда определяются перед любыми другими статическими объектами, использующими их.
Из раздела 3.6.2.1:
Объекты со статической продолжительностью хранения определенные в пространстве имен в том же единицы перевода и динамически инициализируется в порядок, в котором их определение появляется в блоке перевода.
Ответ 1: Это означает, что передача переменных в статический объект constuctor должна быть прекрасной.
Ответ 2: Однако может возникнуть проблема, если переменные ссылаются на нестрочный конструктор статического объекта:
Ни в разделе 3.6.2.1, ни в 3.6.2.3 не указано, в каком порядке статические объекты в разных единицах компиляции инициализируются, если динамическая инициализация выполняется до первого утверждения main
.
Рассмотрим следующее:
// consts.h
#include <string>
const std::string string1 = "ham";
const std::string string2 = "cheese";
// myclass.h
#include <string>
class MyClass
{
public:
MyClass();
MyClass(std::string str);
std::string Get() { return memberString; }
private:
std::string memberString;
}
// myclass.cpp
#include "consts.h"
#include "myclass.h"
MyClass::MyClass() : memberString(string1) {}
MyClass::MyClass(std::string str) : memberString(str) {}
// main.cpp
#include <iostream>
#include "consts.h"
#include "myclass.h"
MyClass myObject1;
MyClass myObject2(string2);
using namespace std;
int main()
{
cout << myObject1.Get(); // might not print "ham"
cout << myObject2.Get(); // will always print "cheese"
}
Так как myclass.cpp
имеет свою собственную копию переменных const
, они могут не инициализироваться при вызове MyClass::MyClass()
.
Итак, да, переменные const
, определенные в файлах заголовков, могут быть использованы таким образом, который подвержен фиаско статической инициализации
Насколько я вижу, это применимо только к переменным, не требующим статической инициализации:
Из стандарта С++ 03, раздел 3.6.2.1:
Объекты типов POD (3.9) со статическими длительность хранения, инициализированная постоянные выражения (5.19) должны быть инициализируется перед любым динамическим выполняется инициализация.
Ответ 2
Ваше первое определение помещает path1
в каждый блок компиляции, который включает config.h
. Чтобы этого избежать, не указывайте переменные в файлах заголовков. Обычно вы объявляете переменные в заголовке как extern
:
extern const std::string path1;
extern const MyClass myclass1;
и определить их в файле реализации, например. config.cpp
:
const std::string path1 = "/xyz/abc";
const MyClass myclass1("test");
Иногда вам нужна постоянная переменная, которая может использоваться только из одного файла реализации. Затем вы можете объявить эту переменную в области файлов как static
.
static const std::string path1 = "/xyz/abc";
static
больше не устаревает. static
и extern
иногда подразумеваются, но я всегда забываю, где и как, поэтому я обычно указываю их явно для всех переменных уровня пространства имен.
Ответ 3
То, что называется фиксом статической инициализации, является проблемой, когда одна переменная уровня пространства имен зависит от значения, назначенного переменной уровня пространства имен, которая ранее была или не была инициализирована. В ваших двух примерах нет такой зависимости, и проблем не должно быть.
Это, с другой стороны, подвержено такому типу ошибок:
// header.h
extern const std::string foo;
// constant.cpp
const std::string foo( "foo" );
// main.cpp
#include "header.h"
const std::string foobar( foo+"bar" );
int main() {
std::cout << foobar << std::endl;
}
Нет гарантии, что foo
будет инициализирован до foobar
, даже если оба они постоянны. Это означает, что поведение программы undefined, и оно может хорошо печатать "foobar", "bar" или die.
Ответ 4
Фиксация статической инициализации относится к статическим переменным, которые зависят друг от друга. Простое определение некоторых переменных static const
не будет источником проблем.