繁体   English   中英

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

[英]C++ global initialization order ignores dependencies?

我认为我的问题最好用代码来描述:

#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) 全局变量的初始化顺序忽略所有依赖项。 这是正确的吗? 是否可以确保在初始化dummy之前调用了Foo的构造函数?

我试图解决的问题是静态填充项目存储库( Foo的静态实例)。 在我目前的尝试中,我正在使用一个宏(除其他外)创建一个全局变量(在匿名命名空间中以避免名称冲突),其初始化触发静态初始化。 也许我从错误的角度解决了我的问题? 有没有更好的选择? 谢谢。

(1) globals 的初始化顺序没有定义

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

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

(2) globals 的初始化顺序忽略所有依赖

对。

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

是的,如果globalFoodummy之前定义并且它们在同一个翻译单元中。

一种选择是拥有一个指向全局实例的静态指针; 在任何动态初始化发生之前,这样的指针将被初始化为 null; 然后addToGlobal可以测试指针是否为空; 如果是,那么这是第一次使用 global 并且addToGlobal可以创建全局Foo

关于初始化的顺序,请阅读这里的答案。

关于如何解决初始化问题,可以在函数中将全局推为静态局部变量。 有标准保证静态局部变量将在第一次调用函数时初始化:

class Foo {
public:
   static Foo& singleton() {
      static Foo instance;
      return instance;
   }
};

然后您的其他全局变量将访问该变量:

Foo::singleton().add();

请注意,这通常不被认为是一个好的设计,而且即使这解决了初始化问题,也不能解决最终化的顺序,因此您应该注意不要在单例被销毁后访问它。

你是对的,翻译单元之间的全局变量的初始化是未定义的。 可以使用单例模式来解决这个问题。 但是,请注意这种设计模式经常被误用。 另请注意,全局变量的顺序或销毁也是未定义的,以防您在析构函数中有依赖项。

为全局变量提供正确初始化顺序的最可靠方法...

1) 初始化顺序取决于传递给链接器的目标文件顺序。 直线或反向 - 无关紧要。 您可以创建测试应用程序来检测它。

2) 使用适当的实用程序(例如nm )来发现每个包含全局变量的对象文件的导入和导出。

3)构建依赖关系图,对目标文件进行排序并构建正确链接所需的顺序。 如果存在,请手动解析循环。

我在 Linux 上的 makefile 中使用了这样的过程。 它有效...

C++ 缺少像Ada 的pragma elaborate那样东西,所以你不能对初始化将发生的顺序做出任何假设。抱歉。 这很糟糕,但这就是设计。

如何让静态全局变量成为初始化为 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 时,我仍然抛出异常,如果在与工厂逻辑不同的编译单元中处理类的注册,就会发生这种情况。

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

重要的是要在此规则中添加注释,即仅声明不定义顺序:

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; 
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM