ProgrammingC++開発者

C++におけるデザインパターン「シングルトン」とは何ですか?正しく実装する方法と主な落とし穴は何ですか?

Hintsage AIアシスタントで面接を突破

回答。

問題の歴史:

シングルトンパターンは、特定のクラスのインスタンスが1つだけ作成されるように制限するために提案されました。これは、グローバルマネージャー(例えば、ロガー、リソースプール、設定者)を実装するために求められていました。

問題:

シングルトンの実装は簡単に見えますが、C++では問題があります:スレッド安全性、破棄の正確性、初期化の順序。

解決策:

シングルトンを実装する最も安全で現代的な方法は、静的関数内に静的ローカル変数を使用する方法で、これにより最初の呼び出し時に初期化が保証され、スレッド安全性が確保され(C++11以降)、正確な破棄が可能です。

コード例:

class Singleton { public: static Singleton& instance() { static Singleton s; return s; } void doSomething() {} private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };

主な特徴:

  • クラスのインスタンスが1つだけ存在することの保証。
  • レイジーなオブジェクト生成とスレッド安全性(C++11以降)。
  • インスタンスのライフタイムと破棄の制御。

トリック質問。

シリアライズやクローンでシングルトンの2番目のインスタンスを作成できますか?

はい。シリアライズ/デシリアライズメソッドを実装したり、コピーコンストラクタを制限せずに手動でclone()を実装した場合、2番目のインスタンスが作成される可能性があります。これを避けるために、すべてのコピー、クローン、およびシリアライズによる復元の方法を禁止する必要があります。

C++98/03標準では、ローカル静的変数を使用してスレッドセーフなシングルトンが正しく実装されますか?

いいえ。C++11以前のローカル静的変数は初期化時のスレッド安全性を保証していませんでした。これにより、2つのスレッドが同時にインスタンス()関数に入ると、複数のインスタンスが作成される可能性があります。C++11以降、これは標準レベルで解決されています。

静的ローカル変数を通じて作成されたシングルトンのインスタンスは、いつ破棄されますか?

オブジェクトは作成の逆順(LIFO)でプログラムの終了時に破棄されます。しかし、デストラクタで既に破棄されたオブジェクトにアクセスがあると問題が発生する可能性があります。

一般的なエラーとアンチパターン

  • static変数の代わりにnewを使用することで、メモリーリークが発生します。
  • コピー/代入を禁止していません。
  • 古い標準ではスレッドを考慮していません。
  • シングルトンインスタンスの保存にshared_ptrやweak_ptrを使用(ユニーク性が侵害される)。

実例

ネガティブケース

ロギングシステムで、開発者はグローバルポインタとnewを使用してシングルトンを実装し、コピーを禁止するのを忘れます。プログラムは動作しますが、マルチスレッド環境では時折異なるロガーのインスタンスが生成され、メッセージが失われます。

長所:

  • 最もシンプルな実装; 単一スレッドモードで迅速に動作。

短所:

  • 潜在的なメモリーリーク。
  • スレッド間でのインスタンスのユニーク性の侵害。
  • 破棄の問題。

ポジティブケース

シングルトンは静的ローカル変数を介して静的関数内で実装され、コピーを禁止されています。スレッド安全性が保証され、プログラムはスケールし、ログは正しく分割されます。

長所:

  • どんな条件でもユニーク性が保証される。
  • 正確な破棄。
  • メモリーのリークがない。

短所:

  • テストが難しくなる(ユニットテスト用にシングルトンを置き換えるのが難しい)。