Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/149.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ C++;全局初始化顺序忽略依赖项?_C++ - Fatal编程技术网

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

C++ C++;全局初始化顺序忽略依赖项?,c++,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:

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

#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;
}
这就是我所期望的,而且似乎是合乎逻辑的

但是,当我交换以下行时:

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) 全局变量的初始化顺序未定义

单个转换单元(源文件)中的全局变量按定义顺序初始化

未指定全局变量在不同转换单位中的初始化顺序

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

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

是,如果在
dummy
之前定义了
globalFoo
,并且它们位于同一翻译单元中


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

如果您是正确的,则未定义翻译单元之间全局变量的初始化。可以使用。但是,请注意,这种设计模式经常被误用。还要注意,如果析构函数中存在依赖项,则全局函数的顺序或销毁也是未定义的。

C++缺少类似的内容,因此您不能对全局函数中的顺序初始化进行任何假设。很抱歉它很烂,但这就是设计。

关于初始化顺序,请阅读答案

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

class Foo {
public:
   static Foo& singleton() {
      static Foo instance;
      return instance;
   }
};
然后,您的其他全局变量将通过以下方式访问该变量:

Foo::singleton().add();

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

让静态全局变量成为初始化为nullptr的指针如何。然后,在另一个全局对象尝试使用该对象之前,请检查其创建情况,必要时进行创建。这对我来说很有用,因为我创建了一个类创建者的全局注册表,在这里可以添加新类,而无需更改处理注册表的文件。 i、 e

类工厂{
静态地图*表格;
静态无效寄存器1(常量字符串和字符串,创建者*创建者);
...
};
...
map*Factory::theTable=nullptr;
无效工厂::注册表1(常量字符串&名称,创建者*创建者){
如果(!theTable)theTable=新地图;
(*表格)[名称]=创建者;
}
这是在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

}
类工厂{
公众:
静态映射表;
静态映射&getTable();
静态无效寄存器1(常量字符串和字符串,创建者*创建者);
}
地图工厂::表格;
映射和工厂::getTable(){
返回表格;
}
无效工厂::寄存器1(常量字符串和字符串,创建者*创建者){
getTable()[theString]=creator;//如果在创建表之前执行,则失败
}
但在尝试向映射中插入条目之前未创建表时,仍会引发异常,如果类的注册在工厂逻辑之外的单独编译单元中处理,则会发生异常

单个转换单元(源文件)中的全局变量按定义顺序初始化

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

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

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

1) 初始化顺序取决于传递给链接器的对象文件顺序。直行还是反行?没关系。您可以创建测试应用程序来检测它

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

3) 构建依赖关系图,对对象文件进行排序,并构建正确链接所需的顺序。手动解析周期(如果存在)


我在Linux上的makefiles中使用了这样的过程。它工作…

通过“在初始化dummy之前是否可以确保调用Foo的构造函数?”我的意思是“除了更改定义的顺序之外”。你的回答让事情变得更清楚了,但它并没有真正“解决”我的问题。在我看来,最好是有一个Foo的静态成员,即存储库。将Foo视为一个存储库(即globalFoo)和用于其他目的并不“感觉正确”。另一方面,如果Foo“拥有”全球回购
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; 
};