Подтвердить что ты не робот

Константные переменные в файле заголовка и фиаско статической инициализации

После прочтения многих вопросов, касающихся инициализации статических переменных, я все еще не уверен, как это относится к переменным 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?

4b9b3361

Ответ 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 не будет источником проблем.