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

时间:2011-01-26 15:44:28

标签: c++ initialization linkage

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

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

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变量具有内部联系,两种情况都应该没有问题?

编辑(由于运动答案)

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

main.cpp

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

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

int main()
{
    ...
}

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

4 个答案:

答案 0 :(得分:12)

您的第一个定义将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不再被弃用。 staticextern有时是隐含的,但我总是忘记在哪里以及如何,所以我通常会为所有命名空间级变量明确指定它们。

答案 1 :(得分:9)

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

关于const static声明:

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

同样根据附件D.2

  

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

关于静态初始化惨败:

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

从第3.6.2.1节:

  

具有静态存储持续时间的对象   在命名空间作用域中定义的相同   翻译单位和动态   初始化应初始化   他们定义的顺序   出现在翻译单元中。

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

答案2:但是,如果从静态对象的非内联构造函数引用变量,则可能会出现问题:

如果在main的第一个语句之前完成动态初始化,则在3.6.2.1和3.6.2.3中都没有指定不同编译单元中静态对象的初始化顺序

请考虑以下事项:

// 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变量可以以易于静态初始化惨败的方式使用

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

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

  

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

答案 2 :(得分: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;
}

无法保证在foo之前初始化foobar,即使两者都是常量。这意味着程序行为是未定义的,它可以很好地打印“foobar”,“bar”或者死亡。

答案 3 :(得分:2)

静态初始化fiasco是指依赖于的静态变量。仅仅定义一些static const变量不会成为问题的根源。