如何等到静态变量的动态初始化完成

时间:2011-06-04 04:57:43

标签: c++

根据其他来源的信息,C ++区分了两种静态变量的初始化:

  • static - 如果通过将变量初始化为可执行文件中的特殊部分而初始化变量。

  • dynamic - 如果计算静态变量的初始值

关于动态初始化构造函数调用的顺序有很多讨论。但我没有找到信息如何等待可执行文件中所有静态变量的所有动态初始化完成。或者从另一方面如何以间接/通用方式手动调用此初始化。

我使用静态变量初始化来实现插件架构的松散耦合。我在plugin1.c内有plugin1.c,plugin2.c ...和静态变量           static bool installed = plugin1_install();

但在主要内容我需要等到所有插件安装完毕。

我在1建议使用同样的东西。作为以下问题的答案

  

我想为我的程序编写一个共享库。但是,在库中的任何其他函数被调用之前,我需要库有一些自我初始化例程...

答案:

  

C ++本身支持全局初始化。你可以这样做:

     

int global_variable=some_global_function();

     

这在C语言中是非法的,但在C ++中是合法的。

我可以在__CTOR_LIST__的帮助下实现我需要的功能吗?

4 个答案:

答案 0 :(得分:1)

没有必要等待。在C ++程序中,启动是单线程的,所以一旦你到达main的第一条指令,所有全局静态存储持续时间变量都已经初始化了。

然而,您所了解的问题是,在启动阶段初始化的顺序几乎没有什么保证。换句话说,如果静态对象的初始化需要使用另一个静态对象,那么你可能会遇到麻烦。

我的建议是尽量避免在启动阶段进行复杂的处理......特别是避免做任何可能失败的事情。原因是在此期间(以及在关闭时的双重期间)系统尚未(或不再)100%正常运行并且调试特别困难。例如,在Windows系统中,关闭期间的段错误通常会被静音,并且某些环境调试在main开始之前无法正常工作。

如果您的初始化阶段只是插件注册,那么可以通过为注册表使用延迟单例模式使其安全:

struct Plugin
{
    Plugin(const std::string& name);
    ...
};

std::map<const char *, Plugin *>& registered_plugins()
{
    static std::map<const char *, Plugin *> directory;
    return directory;
}

Plugin::Plugin(const char * name)
{
    registered_plugins()[name] = this;
}

...

struct MyPlugin1 : Plugin
{
    MyPlugin1() : Plugin("Plugin-1")
    ...
} MyPlugin_instance;

在上面的代码MyPlugin_instance变量将在启动期间创建(在main之前),但已知插件注册表已经正确构造,因为它不是全局的,而是一个静态的函数,以及那些变量在第一次输入范围时初始化。

将全局插件目录声明为静态持续时间全局会有问题,因为如果插件和目录不在同一个编译单元中,则无法保证初始化顺序;因此,插件可能会被构造并尝试在尚未构建的地图中注册自己。

然而,使用这种方法仍然有危险的是访问静态持续时间对象的析构函数中的单例,因为 - 就像构造一样 - 风险是有人会在它已经被销毁后尝试使用注册表。

无论如何,我的建议是尽量保持这种预处理的最低限度;任何非常重要的事情都可能成为大而难以调试问题的根源。如果可能,启动和关闭应该是IMO保持在明确控制和单线程。

答案 1 :(得分:1)

你无法按照自己的方式去做。我之前在C ++中已经完成了这个,并且由于C ++的工作方式存在一些问题,这是你在其他语言中没有的。

首先,无法保证初始化顺序。有人已经从标准中复制并粘贴了该语句,这基本上就是复制段落的含义。

所以我采用的基本方法是使用原型模式。为了遵循开放式原则,您不希望每次添加插件时都必须持续修改一段代码。所以插件需要自己注册(这很难)或者你有一些启动类从共享库加载它们(这要容易得多)。您采取的方法取决于您的要求。如果您熟悉原型设计模式,其余部分可能会有所帮助。

看起来你倾向于他们注册自己,因为你的插件是你的代码的一部分。我强烈建议采用将每个插件放在共享库中的方法,而不是这样做(稍后会解释原因)。使用共享库,您可以让一个类从目录列表中加载共享库,并针对您的插件探测它们。如果在初始化时完成,则在程序的任何部分使用它们之前加载所有插件。

要在程序启动时使插件注册到原型管理器,原型管理器需要是一个单例。您可以创建一个为您执行注册的类,每个插件类的文件都可以定义该注册类的实例。当您添加更多插件时,只要您将该注册类的实例作为全局变量,它们就会自动注册。

现在这是困难的部分。如果您没有对任何插件的实例的显式引用,那么在编译应用程序时(取决于编译器),它可能会在编译时优化。在向原型管理器注册时对它的引用是不够的;没有直接调用插件的实例,因此链接器不会链接未调用的代码。我通过在链接到应用程序的共享库中包含所有插件和原型管理器来解决这个问题。如果它位于共享库中,则链接器无法对其进行优化,因为它不知道使用该共享库的代码是否将引用特定类。 (因此建议每个插件转到一个共享库。)

可能有一种方法可以通过编译器设置强制引用每个插件。我必须在Objective-C中为iPhone应用程序执行此操作。我不喜欢这个,因为这意味着每次添加新插件时都必须记住在编译器设置中添加引用。在我看来,即使你没有修改代码,这也不遵循开放原则。

这也适用于函数指针(因为出于某种原因你可能会这样做)。这将需要对原型模式的创建方式进行一些修改。

我确信这很清楚。 =)希望你使用one-shared-library-per-plugin方法,让自己轻松一点。

答案 2 :(得分:0)

根据ISO14882-2003(3.6.2):

  • 命名空间作用域的静态持续时间对象(包括全局命名空间作用域)的动态初始化是在单个转换单元中按对象定义的顺序执行的。未指定来自不同翻译单元的对象的初始化顺序。

  • 命名空间范围对象的某些特定动态初始化可以在main函数的第一个语句之前执行,也可以在main的第一个语句之后延迟。如果它被延迟,则保证在第一次使用与初始化对象相同的转换单元中定义的任何函数或对象之前执行初始化。

添加

显然要确保命名空间作用域的某些静态持续时间对象已初始化,您只需调用任何函数或使用在同一翻译中定义的任何对象单位(作为您要初始化的对象)在函数main中或直接或间接从函数main调用的任何函数中。

答案 3 :(得分:-1)

不幸的是,该语言不支持这些语义。

静态存储持续时间对象SSDO(静态和动态初始化)仅保证在使用之前进行初始化。该标准在[basic.start.init]中描述了排序约束和保证。

以下描述了动态初始化可能会延迟到main之后。

(n2723)3.6.2非本地对象的初始化[basic.start.init]

第4段

  

实现定义是否在具有静态存储持续时间的命名空间范围的对象的动态初始化(8.5,9.4,12.1,12.6)在主要的第一个语句之前完成。如果初始化被推迟到main的第一个语句之后的某个时间点,它应该在第一次使用与要初始化的对象相同的转换单元中定义的任何函数或对象之前发生。

实际上,只要您的对象没有副作用,那么您可以编写代码,就好像它们都在main之前被初始化一样(因为尝试访问它们会导致它们的初始化(代码不需要显式操作) ))。如果他们有副作用,那么就不要依赖副作用发生。

如果您依赖副作用,那么您可以利用main()的位置 只需将所有SSDO对象声明为与main()相同的文件即可。因为它们在同一个翻译单元中,所以现在必须在输入main之前初始化它们。

这种方式(或其中一个原因)是允许使用动态加载共享库(dll)而不明确地将共享库的概念写入标准。由于在进入main()之前可执行文件中不存在动态加载的共享库,因此不能在main()之前初始化其SSDO。标准的上述部分允许这一事实。虽然它确保任何SSDO在使用之前都会被初始化。

共享库

当然,标准中没有定义共享库(dll),因为这些组件不直接与语言绑定,而是操作系统提供的功能。

话虽如此。大多数操作系统在加载共享库时(通过标准的共享库加载机制,即dlopen())将在函数调用之前初始化所有SSDO以加载共享库返回。在延迟SSDO初始化的情况下,它将遵守上面[basic.start.init]中定义的规则。

初始化顺序:

初始化顺序通常不重要。

除非您在另一个SSDO的初始化(或销毁)期间使用SSDO。然后命令变得很重要,因为你需要确保在使用之前已经创建/初始化了使用过的对象(否则它的数据是垃圾)。

你有几个选择。如果SSDO位于同一个翻译单元中,您可以按照翻译单元中声明的顺序定义初始化顺序:

// File OneFile.cpp
myClass2 gv_2   = some_othere_global_function();
myClass1 gv_1   = some_global_function(); // If this uses gv_2 it must be declared first

如果他们在不同的翻译单元中,那么您无法保证订单:

// File 1.cpp
myClass1 gv_1   = some_global_function(); // If this uses gv_2 you may have a problem.

// File 2.cpp
myClass2 gv_2   = some_othere_global_function();

订单问题可以通过使用函数来解决:

// File 1.cpp
myClass1& get_gv_1()
{
    static myClass1 gv1 = some_global_function();  // This function can only access gv2
                                                   // by calling get_gv_2(). This will of
                                                   // force dynamic creation of the object
                                                   // this it is guaranteed to be initialized.
    return gv1;
}

// File 2.cpp
myClass2& get_gv_2()
{
    static myClass2 gv2 = some_othere_global_function();
 // ^^^^^^
 //    Notice the use of static here.
 //    This means the object is created the first time the function
 //    is called (and destroyed on application exit).
 //
 //    All subsequent calls will just use the same value.
 //    Note the return type. We return a reference to the object.
    return gv2;
}

另见: