これは何?
一度だけ実行したい初期化処理があるときにどうするのがよいか。
C++11 以降で。
まずい例
if ( 初期化処理やるべき? ){ // [A]
初期化処理やるべき? = false; // [B]
初期化();
初期化済み? = true
}
while( ! 初期化済み? ){ちょっと寝る();}
みたいに書きたくなるけど、これは複数スレッドから呼ばれると不幸になることがある。
一番乗りが [A] に到達したあと、 [B] が実行される前に他のスレッドが [A] を通り抜けてしまうかもしれない。
で。どうするか。
案1. atomic_exchange
[A] と [B] をまとめてアトミックに実施できればよいので
if ( std::atomic_exchange(&初期化処理やるべき?, false) ){ // [A] + [B]
初期化();
初期化済み? = true
}
while( ! 初期化済み? ){ちょっと寝る();}
とすればうまくいくと思う。
変数 初期化済み?
を volatile
かつ atomic にすることをお忘れなく。
案2. call_once
とはいえ「一度だけ呼ぶ」という趣旨の関数がすでにあるのでそれを使ったほうが簡単。
static std::once_flag once;
std::call_once(once, 初期化);
この記事を書くまで知らなかったんだけど、複数スレッドから立て続けに std::call_once
呼ぶと、一番乗りの std::call_once
が終わるまで、二番手以降の std::call_once
はブロックされるらしい。便利。
案3. static 変数
static 変数は、C++11 以降であれば複数スレッドからでもちゃんと一回だけ初期化されるので、こんな風にしてもいい。
というか、こう書けるように型を定義するのが無理ない感じなら、これが一番平易だと思う。
struct some_type { some_type(){ 初期化処理(); } };
static some_type some_obj;
まとめ
というわけで
- 一度だけ初期化したい
- 複数のスレッドが初期化コードのあたりに到達するかもしれない
- もちろん未初期化で初期化コードより先に行かれると困る
ということなら、 static 変数か、 std::call_once
がおすすめだよ。