今回は、C++でよく出てくる「シングルトンのインスタンスの作り方(getInstanceの正しい書き方)」について解説していきます。
「getInstance() ってどう書くのが正解?」
「new して返してるけど、これで合ってる?」
「解放はどうすればいいの?」
こんな疑問はありませんか?
シングルトンは「1つしか存在しない」ことを保証するパターンですが、その1つをどう作るかで書き方が何通りもあり、しかも古い書き方が今でも大量に検索に出てきます。ここを間違えるとメモリリークやクラッシュの原因になります。
この記事を読み終えると、あなたはgetInstanceの正しい書き方・古い書き方がダメな理由・スレッド安全性・初期化順序の罠をしっかり理解できると思いますので、ぜひ最後まで読んでいただけると嬉しいです。
なおシングルトンとは何か・どんな場面で使うかはシングルトンパターンとは?の記事で解説しています。この記事はその一歩先、実装編です。
結論:静的ローカル変数で書く
先に答えを出します。C++11以降は、これが最もシンプルで安全です。
#include <iostream>
class GameManager {
public:
// これが定番の書き方
static GameManager& GetInstance() {
static GameManager instance; // 初回呼び出し時に1度だけ作られる
return instance;
}
void AddScore(int s) { score_ += s; }
int GetScore() const { return score_; }
private:
GameManager() = default; // 外から作れないように隠す
~GameManager() = default;
// コピー・ムーブを禁止する(2個目を作らせない)
GameManager(const GameManager&) = delete;
GameManager& operator=(const GameManager&) = delete;
int score_ = 0;
};
int main() {
GameManager::GetInstance().AddScore(100);
std::cout << GameManager::GetInstance().GetScore() << "n";
return 0;
}100
この書き方はMeyers Singleton(マイヤーズのシングルトン)と呼ばれます。ポイントは3つです。
- 関数の中の
staticなので、初めて呼ばれたときに1度だけ作られる - プログラム終了時に自動で片付く(
delete不要) - コンストラクタを
privateにして、外から勝手に作れないようにする
static そのものの意味はstaticとは?の記事もあわせてどうぞ。
なぜ「newして返す」書き方はダメなのか
ネットでよく見かけるのが、こちらの書き方です。
// 古い書き方(おすすめしない)
class GameManager {
public:
static GameManager* GetInstance() {
if (instance_ == nullptr) {
instance_ = new GameManager(); // 誰が delete する?
}
return instance_;
}
private:
static GameManager* instance_;
};
GameManager* GameManager::instance_ = nullptr;動くには動きますが、次の問題があります。
- 解放されない。
deleteを書く場所がなく、デストラクタも呼ばれない - スレッドセーフではない。複数スレッドから同時に呼ぶと2個作られる可能性がある
- コードが長い。静的メンバの定義も別途必要
対して静的ローカル変数版は、C++11以降なら初期化が1度だけであることが規格で保証されており、スレッドセーフです。しかも短い。選ばない理由がありません。
使うときの書き方
参照を返しているので、呼び出し側はこう書けます。
// 毎回書くパターン
GameManager::GetInstance().AddScore(10);
// 何度も使うなら参照で受けると読みやすい
auto& gm = GameManager::GetInstance();
gm.AddScore(10);
gm.AddScore(20);ここで必ず auto&(参照)で受けてください。auto gm = ... と書くとコピーしようとしてコンパイルエラーになります(コピーを禁止しているため)。このエラーは正しく効いている証拠なので、慌てずに & を付ければ大丈夫です。参照については参照(&)とは?の記事をどうぞ。
【重要】私が実際にシングルトンで困った体験談
個人開発で、new して返す古い書き方のシングルトンを使っていたとき、終了時にセーブデータが書き込まれないという不具合にハマりました。
原因は、セーブ処理をデストラクタに書いていたこと。new したきり delete していないので、デストラクタが一生呼ばれないわけです。静的ローカル変数版に書き換えたら、終了時にきちんと呼ばれるようになりました。
もう一つ、もっと厄介だったのが初期化順序の問題です。SoundManager のデストラクタから LogManager を呼んでいたら、終了時にたまにクラッシュする。これは LogManager のほうが先に壊されていたためでした。
静的ローカル変数は「作られた順の逆」で壊れます。つまり後から作られたものが先に壊れる。デストラクタ同士で呼び合うと、この順序に振り回されます。シングルトンのデストラクタから他のシングルトンを触らない、と決めたら安定しました。
シングルトン実装のよくある失敗例と対処法
①コピーを禁止し忘れる
コピーコンストラクタを禁止しないと、GameManager copy = GameManager::GetInstance(); で2個目ができてしまいます。「1つだけ」が崩れるので、= delete を必ず書きましょう。
②デストラクタから他のシングルトンを触る
体験談のとおり、破棄順序に依存してクラッシュします。終了処理が必要なら、デストラクタ任せにせず明示的な Shutdown() を用意して、自分で順番を決めて呼ぶのが安全です。
③何でもシングルトンにする
便利なのでつい増やしがちですが、シングルトンは実質グローバル変数です。どこからでも書き換えられるので、増やすほどどこで壊れたか分からなくなります。本当に1つしか存在しえないものだけに絞りましょう。
注意点
- 実装は静的ローカル変数+参照を返すが定番
- 受け取るときは
auto&(参照)で - 終了処理はデストラクタ任せにしない
まとめ
getInstanceは関数内static+ 参照を返すのが正解- C++11以降はスレッドセーフが保証され、解放も自動
newして返す書き方は解放されず、スレッドセーフでもない- コピー禁止を忘れず、デストラクタ同士で呼び合わない
シングルトンは書き方さえ固定してしまえば、迷うところのないパターンです。この記事のテンプレートをそのままコピーして使ってみてください。

