头文件中的const变量和静态初始化fiasco

Han*_* S. 14 c++ initialization linkage

在阅读了很多关于静态变量初始化的问题之后,我仍然不确定它如何适用const于命名空间级别的变量.

我在构建脚本生成的文件中有以下代码config.h:

static const std::string path1 = "/xyz/abc";
static const std::string path2 = "/etc";
Run Code Online (Sandbox Code Playgroud)

根据我所读到的,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");
Run Code Online (Sandbox Code Playgroud)

这会引起静态初始化的任何问题吗?

如果我理解正确,由于const变量具有内部联系,两种情况都应该没有问题?

编辑:(由于运动答案)

也许我应该提一下,我对以下用例感兴趣:

main.cpp:

#include <config.h>
#include <myclass.h>

std::string anotherString(path1 + myclass1.Get());

int main()
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

关于这个用例的另一个问题:path2在这种情况下,编译器会优化吗?

Phi*_*ipp 12

您的第一个定义path1位于包含的每个编译单元中config.h.为避免这种情况,请不要在头文件中定义变量.通常你会在标题中声明变量extern:

extern const std::string path1;
extern const MyClass myclass1;
Run Code Online (Sandbox Code Playgroud)

并在翻译单元中定义它们,例如config.cpp:

const std::string path1 = "/xyz/abc";
const MyClass myclass1("test");
Run Code Online (Sandbox Code Playgroud)

有时您需要一个只能从一个翻译单元使用的常量变量.然后,您可以将文件范围内的变量声明为static.

static const std::string path1 = "/xyz/abc";
Run Code Online (Sandbox Code Playgroud)

static不再被弃用了.static并且extern有时暗示,但我总是忘记在哪里以及如何,所以我通常明确地为所有命名空间级变量指定它们.

  • 我认为您正在寻找的短语是"翻译单元"而不是实现文件.值应在标题中声明,并在最多一个翻译单元中定义.在这种情况下,由于它们是const并且具有隐含的内部链接,因此在链接时不会得到多重定义的符号错误,但是在每个包含此标题的转换单元中都确定了相同的符号.由于它们具有内部链接,因此它们不会断开链接,但编译器可能无法消除重复项.通过从头文件中删除字符串的定义,我曾经从可执行文件中删除了8 MB. (2认同)

Han*_* S. 9

我试图从C++ 03标准文档中获取必要的信息.这是我发现的:

关于const static声明:

根据3.5.3节在命名空间级别定义的对象,默认情况下声明const具有内部链接.static还声明了一个名称空间级别对象,以便具有内部链接,因此不需要声明对象static const.

还根据附件D.2

在命名空间范围内声明对象时,不推荐使用static关键字(参见3.3.5).

关于静态初始化fiasco:

由于变量是在头文件中定义的,因此它们总是在使用它们的任何其他静态对象之前定义.

从第3.6.2.1节:

在同一翻译单元的命名空间范围内定义并动态初始化的静态存储持续时间的对象应按其定义出现在翻译单元中的顺序进行初始化.

答案1:这意味着变量传递给静态对象构造应该没问题.

答案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"
}
Run Code Online (Sandbox Code Playgroud)

由于myclass.cpp它有自己的const变量副本,因此在MyClass::MyClass()调用时可能不会初始化这些变量.

所以是的,const头文件中定义的变量可以以易于静态初始化惨败的方式使用

据我所知,这仅适用于不需要静态初始化的变量:

从C++ 03标准,第3.6.2.1节:

具有使用常量表达式(5.19)初始化的静态存储持续时间的POD类型(3.9)的对象应在任何动态初始化发生之前初始化.


Dav*_*eas 8

当一个命名空间级别变量依赖于分配给之前可能或未初始化的不同命名空间级别变量的值时,所谓的静态初始化失败是一个问题.在你的两个例子中没有这样的依赖,并且应该没有任何问题.

另一方面,这容易出现这种类型的错误:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

即使两者都是不变的,也无法保证foo之前会被初始化foobar.这意味着程序行为是未定义的,它可以很好地打印"foobar","bar"或者死亡.

  • 无论如何,我会*尝试完全避免它,并通过初始化消除命名空间级别变量.命名空间级别变量的初始化并不简单,它发生在两次传递中,其中从常量实例化的所有*globals*首先获取它们的值,然后在第二次传递中,依赖于非常量的所有内容将根据定义的顺序(在同一翻译单元内),当一个以上的翻译单元链接到一个程序时,以一个未定义的顺序. (2认同)