C ++全局初始化顺序忽略依赖关系?

时间:2010-09-19 15:16:01

标签: c++

我认为我的问题最好在代码中描述:

#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的静态实例)。在我目前的尝试中,我正在使用一个宏(其中包括)创建一个全局变量(在匿名命名空间中以避免名称冲突),其初始化触发静态初始化。也许我是从错误的角度解决我的问题?有更好的选择吗?感谢。

7 个答案:

答案 0 :(得分:34)

  

(1)未定义全局变量的初始化顺序

单个翻译单元中的全局变量 (源文件)按照定义它们的顺序进行初始化。

未指定不同翻译单元中全局变量的初始化顺序。

  

(2)全局变量的初始化顺序忽略了所有依赖项

右。

  

是否可以确保在初始化虚拟之前调用Foo的构造函数?

是的,如果globalFoodummy之前定义了,并且它们位于相同的翻译单元中。

一个选项是拥有一个指向全局实例的静态指针;在进行任何动态初始化之前,这样的指针将被初始化为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; 
};