一个 施瓦茨反击 旨在确保在使用全局对象之前对其进行初始化。
请考虑使用下面显示的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";
}
如果在main()启动后运行Foo :: Foo(),则保证myStream的使用是安全的(即myStream将在使用前初始化),因为注释中提到了文件静态初始化对象。
但是,假设在main()启动之前创建了Foo实例,如果它是全局的则会发生。这显示在这里:
文件Global.cpp:
#include "Foo.h"
Foo foo;
请注意,Global.cpp没有像Foo.cpp那样获得文件静态初始化器对象。在这种情况下,Schwartz计数器如何确保在foo之前初始化MyStream初始化程序(以及MyStream对象本身)?或者Schwartz计数器在这种情况下会失败吗?
指某东西的用途 “施瓦茨专柜” (所谓的Jerry Schwartz设计了IOStreams库的基础知识,因为它现在已经成为标准;请注意,他不能因许多奇怪的选择而受到指责,因为它们被标记在原始设计上)会导致访问对象在他们建造之前。最明显的情况是在构造全局对象期间调用一个函数,该对象使用通过Schwartz计数器构建的自己的全局对象调用另一个翻译单元(我正在使用 std::cout
作为全球被施瓦茨计数器守卫以保持榜样的简短):
// 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计数器不能正常工作:
- 为此使用全局对象时,此对象最终构造两次。虽然这在实践中有效,但我觉得这很难看。解决这个问题的方法是使用a
char
适当大小的缓冲区用于对象的实际定义(这些缓冲区通常会被伪装成名称作为正确类型的对象)。然而,在这两种情况下,事情都很混乱。
- 当Schwartz计数器保护的全局对象被用于许多翻译单元时(就像它的情况一样)
std::cout
这可能会导致显着的启动延迟:编写良好的代码通常不使用任何全局初始化,但Schwartz计数器需要为每个需要加载的目标文件运行一段代码。
我个人已经得出结论,这种技术是一种 俏皮的 想法,但它在实践中不起作用。我使用了三种方法:
- 不要使用全局对象。这使得整个讨论过时并且在并发代码中工作得最好。在绝对需要全局资源的地方,通过引用返回并使用初始化的函数静态对象
std::call_once()
是一个更好的选择。
- 在链接可执行文件(例如,最后一个)时将全局对象放置在适当的位置会使其首先被初始化。我过去曾经尝试过这个,然后我发现我可以在所有我关心的系统上正确放置目标文件。这里的主要缺点是没有保证,在编译器版本之间切换时可能会发生变化。但是对于C ++标准库,这是可以接受的(当我这样做时,我只关心全局流对象)。
- 将全局对象放入专用共享库中:加载共享库时,将执行其初始化代码。只有在初始化完成后,共享库中的对象才可用。我发现它可靠地运行但需要额外的库。