以下程序在5个文件中。输出是0 32,而不是14 32,因此在不调用构造函数的情况下构造对象。这怎么可能?
character.h:
#ifndef CHARACTER_H
#define CHARACTER_H
class Character
{
public:
Character() {}
class Settings
{
public:
int size_;
Settings():
size_(14)
{}
};
const static Settings DEFAULT_SETTINGS;
};
#endif // CHARACTER_H
character.cpp:
#include "character.h"
const Character::Settings Character::DEFAULT_SETTINGS;
word.h
#ifndef WORD_H
#define WORD_H
#include <iostream>
#include "character.h"
class Word
{
public:
Word() {}
class Settings
{
public:
Character::Settings characterSettings_;
int length_;
Settings():
length_(32)
{
characterSettings_ = Character::DEFAULT_SETTINGS;
}
};
static const Settings DEFAULT_SETTINGS;
void write(Settings settings = DEFAULT_SETTINGS) // this default parameter is
// constructed without a
// constructor call
{
std::cout << settings.characterSettings_.size_ << std::endl;
std::cout << settings.length_ << std::endl;
}
};
#endif // WORD_H
word.cpp
#include "word.h"
const Word::Settings Word::DEFAULT_SETTINGS;
main.cpp
#include "word.h"
int main(int argc, char *argv[])
{
Word member;
member.write();
return 1;
}
这被称为静态初始化失败。对于在不同转换单元中定义的具有静态持续时间的变量,构造函数的执行基本上没有固定的顺序。在这种特定情况下,Word::DEFAULT_SETTINGS
已经在Character::DEFAULT_SETTINGS
之前构造,并且因此已经读取了静态持续时间的变量在实际初始化之前具有的0
值。如果你想看到一些有趣的东西,转储Character::DEFAULT_SETTINGS
的内容,你会发现奇怪地它是14
静态初始化顺序问题可以通过一些巧妙的(并非真正的)头文件技巧来解决。
基本上,这个想法的要点是这样的(我已经在实践中实现了好几次,尽管是在亿万年前,所以这不仅仅是目前的一些空闲堆栈溢出想法):
基本上,您将静态对象(如foo_class g_foo;
)的定义放在foo.h
头文件的一个特殊分隔区域中:
#ifndef FOO_H
#define FOO_H
#include "bar.h" // dependency: crucial part!
// ... declares foo_class ...
// foo is a client of bar
// Now somewhere near the bottom:
#ifdef DEFINE_GLOBAL_SINGLETONS
foo_class g_foo;
#endif
现在,您设置了一个专门指定的singleton存储库源文件,其中包含了所有的头文件。
// singletons.cc
#define DEFINE_GLOBAL_SINGLETONS
#include "foo.h"
#include "bar.h" // note deliberately wrong order!
头文件包含依赖项和保护将导致头被包括在模块依赖项顺序中,因此DEFINE_GLOBAL_SINGLETONS
节将以正确的顺序添加到翻译单元中。
在单个翻译单元中,C++要求从上到下构造对象。
因此,您可以获得类似Ada/Modula的模块初始化顺序:在用户之前使用模块。foo
用户bar
和soo的bar
单例(如果有的话)在foo
单例之前被初始化。
现在的缺点是:每次头更改时都要重新编译singleton存储库。