導入
プログラムの規模が大きくなると、起動時の処理時間が長くなり、ユーザー体験を損なうことがあります。特に、複雑なオブジェクトをグローバルスコープで宣言している場合、実際に使われるかどうかにかかわらずプログラム起動時に構築コストが発生してしまいます。この課題を解決するのが「遅延初期化(Lazy Initialization)」です。本記事では、C++11から保証された安全かつ高性能な初期化手法を解説します。
基礎知識
「遅延初期化」とは、オブジェクトをプログラム開始時に生成するのではなく、初めてアクセスされた瞬間に生成する手法です。
かつてのC++では、マルチスレッド環境下でこれを安全に行うにはミューテックス(排他制御)が不可欠であり、それがパフォーマンスのボトルネックとなっていました。しかし、C++11以降は「静的ローカル変数の初期化保証」という強力な仕様が導入され、コンパイラがスレッドセーフな初期化を自動的に管理してくれるようになりました。
実装/解決策
最も推奨される解決策は、関数内に static 変数を定義することです。
C++11の規格では、複数のスレッドが同時に静的ローカル変数の初期化を開始しようとした場合、最初のスレッドが構築を完了するまで他のスレッドは待機し、二重構築が起きないようコンパイラが内部的にガード変数を挿入します。これを「Meyers’ Singleton」と呼ぶこともあります。
サンプルプログラム
以下のコードは、重い処理を伴うオブジェクトを、必要な瞬間に一度だけ生成する例です。そのままコンパイルして動作を確認できます。
#include
include
// 重い処理を想定したクラス
class HeavyResource {
public:
HeavyResource() {
std::cout << "重い初期化処理を実行中..." << std::endl;
}
void doSomething() {
std::cout << "リソースを使用中" << std::endl;
}
};
// 遅延初期化を行う関数
HeavyResource& getResource() {
// この static 変数は、初めてこの関数が呼ばれた時に一度だけ初期化されます。
// C++11以降、この初期化はスレッドセーフであることが保証されています。
static HeavyResource instance;
return instance;
}
int main() {
std::cout << "プログラム開始" << std::endl;
// 実際に使用するまで、HeavyResourceのコンストラクタは呼ばれません
getResource().doSomething();
return 0;
}
応用・注意点
この手法は非常に強力ですが、いくつか注意が必要です。
1. 破棄のタイミング
静的ローカル変数は、プログラム終了時に破棄されます。依存関係がある複数のグローバルオブジェクトが存在する場合、破棄の順序に注意が必要です。プログラム終了時にアクセスされると、既に破棄済みである可能性があります。
2. コンストラクタでの例外
初期化中に例外が発生した場合、初期化は失敗とみなされ、次回アクセス時に再度初期化が試行されます。コンストラクタ内で確実にリソースを確保できるよう設計してください。
3. パフォーマンス
ミューテックスを自分で書くよりも、コンパイラが挿入するガード変数の仕組みの方が圧倒的に高速です。不必要にロックを使用せず、この標準的な言語仕様を積極的に活用しましょう。

コメント