この投稿は、最後の短い質問では長すぎるように思われるかもしれません。ただし、思いついたデザインパターンについても説明する必要があります。多分それは一般的に使用されていますが、私はそれを見たことがありません(または多分それは単に機能しません:)。
まず、これが(私の理解では)「静的初期化順序の大失敗」のために未定義の動作をするコードです。問題は、Spanish::s_englishToSpanishの初期化がEnglish::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;
}
静的初期化順序の問題を解決するために、最初に使用するときに構築するイディオムを使用し、これらの静的初期化を次のように関数ローカルにします。
ファイル: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];
}
};
しかし今、私たちは別の問題を抱えています。関数ローカルの静的データのため、これらのクラスはどちらもスレッドセーフではありません。これを解決するために、両方のクラスに静的メンバー変数とその初期化関数を追加します。次に、この関数内で、関数ローカル静的データを持つ各関数を1回呼び出すことにより、すべての関数ローカル静的データの初期化を強制します。したがって、事実上、プログラムの開始時にすべてを初期化していますが、それでも初期化の順序を制御しています。これで、クラスはスレッドセーフになるはずです。
ファイル: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();
そして、ここに質問があります:一部のコンパイラが、ローカルの静的データを持つ関数(この場合はコンストラクタ)への呼び出しを最適化する可能性はありますか?したがって、問題は「副作用がある」と正確に何を意味するのかということです。これは、私の理解では、コンパイラがそれを最適化することを許可されていないことを意味します。コンパイラに関数呼び出しを無視できないと思わせるのに十分な関数ローカル静的データがありますか?