スレッドセーフクラスの静的初期化を順序付けました
-
27-10-2019 - |
質問
この投稿は、終了時の短い質問だけでは長く長く見えるかもしれません。しかし、私が思いついたばかりのデザインパターンを説明する必要があります。たぶんそれは一般的に使用されているかもしれませんが、私はそれを見たことがありません(または、それはただ機能しないだけかもしれません:)。
まず、(私の理解のために)「静的な初期化順序FIASCO」のために未定義の動作を持っているコードがあります。問題は、スペイン語の初期化:: 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;
}());
ファイル:スペイン語
#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;
}
静的初期化の順序の問題を解決するために、コンストラクトオンファースト使用イディオムを使用し、それらの静的初期化をfunction-localにします。
ファイル: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];
}
};
ファイル:スペイン語
#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();
ファイル:スペイン語
#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();
質問は次のとおりです。一部のコンパイラが、ローカル静的データを持つ関数(この場合はコンストラクター)への呼び出しを最適化する可能性がありますか?したがって、問題は、「副作用を持つ」ことに正確に存在するものです。これは、私の理解では、コンパイラがそれを最適化することを許可されていないことを意味します。コンパイラに関数呼び出しが無視できないと考えるのに十分な関数ローカルの静的データを持っているのですか?
解決
セクション1.9「プログラム実行」[intro.execution] c ++ 11標準のセクション
1この国際標準のセマンティックな説明は、パラメーター化された非決定的抽象マシンを定義します。 ...以下で説明するように、抽象マシンの観測可能な動作を(のみ)エミュレートするには、適合の実装が必要です。
...5適合した実装は、適切に形成されたプログラムを実行すると、同じプログラムと同じ入力を使用して、抽象マシンの対応するインスタンスの可能な実行の1つと同じ観測可能な動作を生成するものとします。
...8適合実装に関する最小要件は次のとおりです。
- 揮発性オブジェクトへのアクセスは、抽象マシンのルールに従って厳密に評価されます。
- プログラム終了時に、ファイルに書き込まれたすべてのデータは、抽象セマンティクスに従ってプログラムの実行が生成された可能性のある結果の1つと同一でなければなりません。
- インタラクティブデバイスの入力と出力のダイナミクスは、プログラムが入力を待つ前に実際に出力を促すような方法で行われるものとします。インタラクティブなデバイスを構成するものは、実装定義です。
これらは集合的にと呼ばれます 観察可能な動作 プログラムの。
...12揮発性Glalue(3.10)によって指定されたオブジェクトにアクセスする、オブジェクトの変更、ライブラリI/O関数の呼び出し、またはそれらの操作のいずれかを実行する関数の呼び出しはすべてです 副作用, 、実行環境の状態の変化です。
また、3.7.2「自動ストレージ期間」[Basic.stc.auto
3自動ストレージ期間の変数に初期化または副作用があるデストラクタがある場合、ブロックの終了前に破壊されたり、クラスオブジェクトを除いて、使用されていないように見える場合でも最適化として排除するものではありません。または、そのコピー/移動は、12.8で指定されているように排除される場合があります。
12.8-31は、ここでは無関係だと思うコピーエリジョンについて説明しています。
したがって、問題は、ローカル変数の初期化に副作用があり、それが最適化されないようにするかどうかです。動的オブジェクトのアドレスを使用して静的変数の初期化を実行できるため、十分な副作用を生成すると思います(たとえば、オブジェクトを変更します)。また、揮発性オブジェクトを使用して操作を追加することで、排除できない観察可能な動作を導入できます。
他のヒント
わかりますが、一言で言えば:
クラスの静的メンバーが公開する必要がある理由はわかりません - それらは実装の詳細です。
それらをプライベートにするのではなく、コンピレーションユニットのメンバー(クラスを実装するコード)のメンバーにします。
使用する
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();
そして、いくつかのインターフェイスを定義してそれを取得します。