Schwartz counter旨在确保在使用全局对象之前对其进行初始化。

请考虑使用以下所示的Schwartz计数器。

文件Foo.h:

class Foo
{
   Foo::Foo();
};

文件Foo.cpp:
#include "Foo.h"

// Assume including Mystream.h provides access to myStream and that
// it causes creation of a file-static object that initializes
// myStream (aka a Schwartz counter).
#include "MyStream.h"

Foo::Foo()
{
   myStream << "Hello world\n";
}

如果Foo::Foo()在main()启动之后运行,则由于注释中提到了文件静态的初始化对象,因此保证使用myStream是安全的(即myStream将在使用前进行初始化)。

但是,假设Foo实例是在main()开始之前创建的,就像它是全局的一样。如图所示:

文件Global.cpp:
#include "Foo.h"

Foo foo;

请注意,Global.cpp不会像Foo.cpp那样获得文件静态初始化程序对象。在这种情况下,Schwartz计数器如何确保在foo之前初始化MyStream初始化程序(以及MyStream对象本身)?还是在这种情况下Schwartz计数器会失败?

最佳答案

可以使用"Schwartz counters"(以Jerry Schwartz的名字命名,因为Jerry Schwartz设计了IOStreams库的基本知识,现在已经成为标准;请注意,不能将许多奇怪的选择归咎于他,因为这些选择被标记到了原始设计上)在构造对象之前访问对象。最明显的情况是在构造全局对象的过程中调用一个函数,该函数使用通过Schwartz计数器构造的自身全局变量来调用另一个翻译单元(我使用std::cout作为Schwartz计数器保护的全局变量,以使示例简短):

// file a.h
void a();

// file a.cpp
#include <iostream>
void a() { std::cout << "a()\n"; }

// file b.cpp
#include <a.h>
struct b { b() { a(); } } bobject;

如果文件b.cpp中的全局对象先于文件a.cpp中的全局对象,并且如果std::cout是通过Schwartz计数器以a.cpp为第一个实例构造的,则此代码将失败。 Schwartz计数器运行不佳的原因至少有两个:
  • 为此使用全局对象时,该对象最终被构造两次。尽管这样做正确可行,但我认为这很丑。一种解决方法是对对象的实际定义使用大小合适的char缓冲区(这些对象通常会被误认为其名称是正确类型的对象)。但是,在这两种情况下,事情都是困惑的。
  • 如果在许多翻译单元中使用Schwartz计数器保护的全局对象(对于std::cout而言),则可能会导致启动延迟:编写良好的代码通常不使用任何全局初始化,而使用Schwartz计数器需要为每个需要加载的目标文件运行一段代码。

  • 我个人得出的结论是,该技术是一个不错的主意,但实际上不起作用。我使用三种方法代替:
  • 不要使用全局对象。这使整个讨论变得过时,尤其在并发代码中效果最佳。在绝对需要全局资源的情况下,通过引用返回并使用std::call_once()初始化的函数静态对象是更好的选择。
  • 在链接可执行文件(例如,最后一个)时将全局对象放置在适当的位置会导致它首先被初始化。我过去曾对此进行过试验,然后发现我可以将对象文件适当地放置在需要维护的所有系统上。这里的主要缺点是无法保证,并且在编译器版本之间切换时可能会发生变化。但是,对于C++标准库,这是可以接受的(并且在执行此操作时,我只关心全局流对象)。
  • 将全局对象放入专用的共享库中:加载共享库时,将执行其初始化代码。共享库中的对象仅在初始化完成后才可用。我发现这可靠地工作,但需要一个额外的库。
  • 关于c++ - 通过Schwartz计数器进行C++静态初始化,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/9251763/

    10-11 19:29