我认为我的问题最好在代码中描述:
#include <stdio.h>
struct Foo;
extern Foo globalFoo;
struct Foo {
Foo() {
printf("Foo::Foo()\n");
}
void add() {
printf("Foo::add()\n");
}
static int addToGlobal() {
printf("Foo::addToGlobal() START\n");
globalFoo.add();
printf("Foo::addToGlobal() END\n");
return 0;
}
};
Foo globalFoo;
int dummy = Foo::addToGlobal();
int main() {
printf("main()\n");
return 0;
}
以上打印(使用gcc 4.4.3):
Foo::Foo()
Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
main()
这是我所期待的,似乎合乎逻辑。
但是,当我交换以下行时:
Foo globalFoo;
int dummy = Foo::addToGlobal();
进入这个:
int dummy = Foo::addToGlobal();
Foo globalFoo;
程序输出以下内容:
Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
Foo::Foo()
main()
似乎使用尚未构建的实例调用Foo
的实例方法!像在全局范围内移动变量声明一样简单的事情正在影响程序的行为,这使我相信(1)没有定义全局变量的初始化顺序和(2)全局变量的初始化顺序忽略所有依赖项。它是否正确?在初始化Foo
之前是否可以确保调用dummy
的构造函数?
我想解决的问题是静态填充项目的存储库(Foo
的静态实例)。在我目前的尝试中,我正在使用一个宏(其中包括)创建一个全局变量(在匿名命名空间中以避免名称冲突),其初始化触发静态初始化。也许我是从错误的角度解决我的问题?有更好的选择吗?感谢。
答案 0 :(得分:34)
(1)未定义全局变量的初始化顺序
单个翻译单元中的全局变量 (源文件)按照定义它们的顺序进行初始化。
未指定不同翻译单元中全局变量的初始化顺序。
(2)全局变量的初始化顺序忽略了所有依赖项
右。
是否可以确保在初始化虚拟之前调用Foo的构造函数?
是的,如果globalFoo
在 dummy
之前定义了,并且它们位于相同的翻译单元中。
一个选项是拥有一个指向全局实例的静态指针;在进行任何动态初始化之前,这样的指针将被初始化为null;然后addToGlobal
可以测试指针是否为空;如果是,那么这是第一次使用全局,addToGlobal
可以创建全局Foo
。
答案 1 :(得分:12)
按照初始化顺序,阅读答案here。
关于如何解决初始化问题,可以将全局推送到函数中的静态局部变量。标准保证静态局部变量将在第一次调用函数时初始化:
class Foo {
public:
static Foo& singleton() {
static Foo instance;
return instance;
}
};
然后您的其他全局变量将访问变量:
Foo::singleton().add();
请注意,这通常不被认为是一个好的设计,并且即使这解决了初始化问题,它也无法解决最终化的顺序,因此在销毁之后应该注意不要访问单例
答案 2 :(得分:1)
你是对的,翻译单元之间的全局变量初始化是未定义的。可以使用singleton pattern解决这个问题。但是,请注意,这种设计模式经常被误用。还要注意,如果你在析构函数中有依赖关系,那么全局变量的顺序或破坏也是未定义的。
答案 3 :(得分:1)
为全局变量提供正确的初始化顺序的最可靠方法...
1)初始化顺序取决于传递给链接器的目标文件的顺序。正向或反向-无关紧要。您可以创建测试应用程序来检测它。
2)使用适当的实用程序(例如nm
)来查找每个包含全局对象的目标文件的导入和导出。
3)构建依赖关系图,对目标文件进行排序,并构建正确链接所需的顺序。手动解决循环(如果存在)。
我在Linux上的makefile中使用这样的过程。可以...
答案 4 :(得分:0)
C ++缺少类似Ada's pragma elaborate
的内容,所以你不能对启动发生的订单做任何假设。抱歉。它很糟糕,但这就是设计。
答案 5 :(得分:0)
如何将静态全局变量作为初始化为nullptr的指针。然后在另一个全局对象尝试使用该对象之前,检查其创建并在需要时创建。这对我来说是有用的,可以创建一个类创建者的全局注册表,可以在不改变处理注册表的文件的情况下添加新类。 即。
class Factory {
static map<string, Creator*>* theTable;
static void register1(const string& string, Creator* creator);
...
};
...
map<string, Creator*>* Factory::theTable= nullptr;
void Factory::register1(const string& theName, Creator* creator) {
if (!theTable) theTable=new map<string, Creator*>;
(*theTable)[theName]=creator;
}
这在Visual Studio 2015中与VC ++一起编译和使用。
我之前尝试过使用
class Factory {
public:
static map<string, Creator*> theTable;
static map<string, Creator*>& getTable();
static void register1(const string& string, Creator* creator);
}
map<string, Creator*> Factory::theTable;
map<string, Creator*>& Factory::getTable() {
return theTable;
}
void Factory::register1(const string& theString, Creator* creator) {
getTable()[theString]=creator; // fails if executed before theTable is created
}
但是在尝试将一个条目插入到映射之前未创建theTable时仍然会抛出异常,如果在与工厂逻辑的单独编译单元中处理类的注册,则会发生这种情况。
答案 6 :(得分:0)
单个翻译单元(源文件)中的全局变量按照定义顺序进行初始化。
重要的是要在此规则中添加注释,即仅声明不能定义顺序:
extern Foo globalFoo; // or just a ref that is defined at a single place
extern Foo & globalFooRef;
或作为静态成员
struct Global
{
static Foo globalFoo; // or just a ref that is defined at a single place
static Foo & globalFooRef;
};