代码之家  ›  专栏  ›  技术社区  ›  strager

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

c++
  •  15
  • strager  · 技术社区  · 14 年前

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

    #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 ?

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

    7 回复  |  直到 6 年前
        1
  •  12
  •   Community prosti    7 年前

    按照初始化的顺序,读取答案 here .

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

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

    然后您的其他全局变量将访问变量,如下所示:

    Foo::singleton().add();
    

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

        2
  •  33
  •   James McNellis    14 年前

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

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

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

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

    正确的。

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

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

    一个选项是有一个指向全局实例的静态指针;这样的指针将在任何动态初始化发生之前初始化为空; addToGlobal 然后可以测试指针是否为空;如果为空,则是第一次使用全局,并且 附加语言 可以创建全局 Foo .

        3
  •  1
  •   rlbond    14 年前

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

        4
  •  1
  •   Herman Zu    6 年前

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

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

    2)使用适当的公用设施( nm 例如)发现包含全局的每个对象文件的导入和导出。

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

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

        5
  •  0
  •   T.E.D.    14 年前

    C++缺乏任何类似的东西 Ada's pragma elaborate 因此,您不能对将要发生的订单初始化进行任何假设。对不起的。很糟糕,但这就是设计。

        6
  •  0
  •   Jonathan Harris    7 年前

    让静态全局变量成为初始化为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
    
    }
    

    但是,在尝试向映射中插入一个条目之前,如果在工厂逻辑的独立编译单元中处理类的注册,那么在没有创建表时,我仍然会抛出异常。

        7
  •  0
  •   shpc    6 年前

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

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

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