有序的线程安全类的静态初始化

这篇文章可能看起来太长,只是在最后的短小问题。 但是我也需要描述一下我刚刚提出的devise模式。 也许它是常用的,但我从来没有见过它(或者也许它只是不工作:)。

首先,这是一个代码(据我了解)由于“静态初始化顺序失败”而具有未定义的行为。 问题是,西class牙语:: s_englishToSpanish的初始化依赖于英语:: s_numberToStr,它们都是静态初始化的,并且在不同的文件中,所以这些初始化的顺序是未定义的:

文件:English.h

#pragma once #include <vector> #include <string> using namespace std; struct English { static vector<string>* s_numberToStr; string m_str; explicit English(int number) { m_str = (*s_numberToStr)[number]; } }; 

文件:English.cpp

 #include "English.h" vector<string>* English::s_numberToStr = new vector<string>( /*split*/ []() -> vector<string> { vector<string> numberToStr; numberToStr.push_back("zero"); numberToStr.push_back("one"); numberToStr.push_back("two"); return numberToStr; }()); 

文件:Spanish.h

 #pragma once #include <map> #include <string> #include "English.h" using namespace std; typedef map<string, string> MapType; struct Spanish { static MapType* s_englishToSpanish; string m_str; explicit Spanish(const English& english) { m_str = (*s_englishToSpanish)[english.m_str]; } }; 

文件:Spanish.cpp

 #include "Spanish.h" MapType* Spanish::s_englishToSpanish = new MapType( /*split*/ []() -> MapType { MapType englishToSpanish; englishToSpanish[ English(0).m_str ] = "cero"; englishToSpanish[ English(1).m_str ] = "uno"; englishToSpanish[ English(2).m_str ] = "dos"; return englishToSpanish; }()); 

文件:StaticFiasco.h

 #include <stdio.h> #include <tchar.h> #include <conio.h> #include "Spanish.h" int _tmain(int argc, _TCHAR* argv[]) { _cprintf( Spanish(English(1)).m_str.c_str() ); // may print "uno" or crash _getch(); return 0; } 

为了解决静态初始化顺序问题,我们使用了首先使用的构build方法,并使得这些静态初始化函数是局部的:

文件:English.h

 #pragma once #include <vector> #include <string> using namespace std; struct English { string m_str; explicit English(int number) { static vector<string>* numberToStr = new vector<string>( /*split*/ []() -> vector<string> { vector<string> numberToStr_; numberToStr_.push_back("zero"); numberToStr_.push_back("one"); numberToStr_.push_back("two"); return numberToStr_; }()); m_str = (*numberToStr)[number]; } }; 

文件:Spanish.h

 #pragma once #include <map> #include <string> #include "English.h" using namespace std; struct Spanish { string m_str; explicit Spanish(const English& english) { typedef map<string, string> MapT; static MapT* englishToSpanish = new MapT( /*split*/ []() -> MapT { MapT englishToSpanish_; englishToSpanish_[ English(0).m_str ] = "cero"; englishToSpanish_[ English(1).m_str ] = "uno"; englishToSpanish_[ English(2).m_str ] = "dos"; return englishToSpanish_; }()); m_str = (*englishToSpanish)[english.m_str]; } }; 

但现在我们还有另一个问题。 由于函数本地静态数据,这两个类都不是线程安全的。 为了解决这个问题,我们为这两个类添加了一个静态成员variables和一个初始化函数。 然后在这个函数内部,我们强制所有函数本地静态数据的初始化,通过调用每个具有函数本地静态数据的函数。 因此,我们有效地在程序开始时初始化所有的东西,但仍然控制着初始化的顺序。 所以现在我们的类应该是线程安全的:

文件:English.h

 #pragma once #include <vector> #include <string> using namespace std; struct English { static bool s_areStaticsInitialized; string m_str; explicit English(int number) { static vector<string>* numberToStr = new vector<string>( /*split*/ []() -> vector<string> { vector<string> numberToStr_; numberToStr_.push_back("zero"); numberToStr_.push_back("one"); numberToStr_.push_back("two"); return numberToStr_; }()); m_str = (*numberToStr)[number]; } static bool initializeStatics() { // Call every member function that has local static data in it: English english(0); // Could the compiler ignore this line? return true; } }; bool English::s_areStaticsInitialized = initializeStatics(); 

文件:Spanish.h

 #pragma once #include <map> #include <string> #include "English.h" using namespace std; struct Spanish { static bool s_areStaticsInitialized; string m_str; explicit Spanish(const English& english) { typedef map<string, string> MapT; static MapT* englishToSpanish = new MapT( /*split*/ []() -> MapT { MapT englishToSpanish_; englishToSpanish_[ English(0).m_str ] = "cero"; englishToSpanish_[ English(1).m_str ] = "uno"; englishToSpanish_[ English(2).m_str ] = "dos"; return englishToSpanish_; }()); m_str = (*englishToSpanish)[english.m_str]; } static bool initializeStatics() { // Call every member function that has local static data in it: Spanish spanish( English(0) ); // Could the compiler ignore this line? return true; } }; bool Spanish::s_areStaticsInitialized = initializeStatics(); 

这里的问题是:有可能是一些编译器可能会优化掉那些有本地静态数据的函数(在这种情况下是构造函数)吗? 所以问题是什么恰恰等于“有副作用”,这对我的理解意味着编译器不允许优化它。 有足够的function本地静态数据,使编译器认为函数调用不能被忽略?

C ++ 11标准的1.9节“程序执行”[intro.execution]说明了这一点

1本标准中的语义描述定义了一个参数化的非确定性抽象机器。 符合实现需要模仿(只)抽象机器的可观察行为,如下所述。

执行一个格式良好的程序的一致性实现应该产生与具有相同程序和相同输入的抽象机器的相应实例的一个可能执行相同的可观察行为。

8符合实施的最低要求是:
– 访问易失性对象严格按照抽象机器的规则进行评估。
– 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。
– 交互设备的输入和输出动态应该以这样一种方式进行,即在程序等待输入之前实际提供提示输出。 什么构成交互设备是由实现定义的。
这些统称为程序的可观察行为

12访问由volatile glvalue(3.10)指定的对象,修改对象,调用库I / O函数,或者调用执行这些操作的函数都是副作用 ,这些副作用是执行环境状态的变化。

而且,在3.7.2“自动存储时间”[basic.stc.auto]中也是这样说的

3如果具有自动存储持续时间的变量具有初始化或具有副作用的析构函数,则在其块结束之前不应将其销毁,即使看起来未被使用,也不应将其作为优化来除去,除非类对象或者其复制/移动可能按照12.8的规定被取消。

12.8-31描述了我认为在这里不相关的复制elision。

所以问题是你的本地变量的初始化是否有副作用,防止它被优化。 因为它可以用动态对象的地址执行静态变量的初始化,所以我认为它会产生足够的副作用(例如修改一个对象)。 你也可以添加一个volatile对象的操作,从而引入一个无法消除的可观察行为。

好的,简而言之:

  1. 我看不出为什么这个类的静态成员需要公开 – 它们是实现细节。

  2. 不要让它们成为私有的,而是让它们成为编译单元(实现你的类的代码)的成员。

  3. 使用boost::call_once来执行静态初始化。

第一次使用初始化相对容易执行顺序,这是非常难以执行的破坏。 但请注意,call_once中使用的函数不能抛出异常。 因此,如果它可能会失败,你应该离开某种失败的状态,并检查通话后。

(我会假设在你真实的例子中,你的负载不是你硬编码的,但更有可能你加载某种动态表,所以你不能只创建一个内存数组)。

为什么不隐藏英文:: s_numberToStr在公共静态函数的后面,并且完全跳过构造函数的语法? 使用DCLP来确保线程安全。

我强烈建议避免类初始化涉及不重要的副作用的类静态变量。 作为一般的设计模式,它们往往会造成比解决问题更多的问题。 无论您在这里担心的性能问题需要证明,因为我怀疑它们在现实环境下是可衡量的。

也许你需要做额外的工作来控制init命令。 喜欢,

 class staticObjects { private: vector<string>* English::s_numberToStr; MapType* s_englishToSpanish; }; static staticObjects objects = new staticObjects(); 

然后定义一些接口来检索它。