すくい
だから、始めに、レーキ自体。 継承されたプロジェクトでは、単独のクラスが積極的に使用されました。 10個以上ありました。 動的なインスタンス化は、最初の呼び出しで使用されました。 getInstanceメソッドは次のとおりでした
Singleton* Singleton::getInstance() { if (sInstance == NULL) { sInstance = new Singleton; } return sInstance; }
そして、メモリリークのハントが始まりました。 プロファイラーが多数の誤った「リーク」を与えたため、Lonersの破壊は提供されませんでしたが、実際の問題を見つけることはほとんど不可能でした。 はい、検索したくありませんでした。 したがって、destroyInstanceメソッドを記述することにしました。これにより、要求に応じて単一クラスのインスタンスを破棄できます。
Singleton-logicの実装が別のテンプレートクラスに移動されたという事実により、状況は大幅に簡素化されました。グローバルに変更する必要はありませんでした。 削除メソッドと各ローンの呼び出しを追加するだけで十分でした...そして、理論は機能しました。 基本的に彼女の2つのポイント
- lonersのクラスによっては、クラスおよびモジュールのインターフェースからは明らかではないため、直接実装を調査する必要があることを検出する必要があります。
- シングルトンクラスには明示的な所有者はありません。 それが彼が孤独な理由です。 それから苦しみます。
destroyInstanceをすべて呼び出した後、実際のリークを探すためにプロファイラーを開き、...誤検出の同じ海に突入しました! 破壊者が経験したにも関わらず、大部分の孤独な人々は、彼ら自身の喜びのために、そしてそれ以上に生きました。 まず、いつものように、コンパイラは非難されました。 そして、いつものように、本当の問題は彼ら自身の手にありました。
すべてが正しく破壊されました。 しかし、制御されていない依存関係がloners間に存在していました。 多くの場合、デストラクタでは、一方が他方によって使用されました。 また、getInstanceはクラスの既存のインスタンスを返すだけでなく、必要に応じて新しいインスタンスも作成したため、灰からの大規模な制御されない反乱が発生し始めました。 アレクサンドレスクの本をすぐに思い出しました[1]。 私だけとは異なり、彼は故意にネクロマンシーに取り組んでいました(セクション6.6、ほぼ象徴的)。
状況を修正する必要がありました。 最初の考えは、インスタンスを破棄するときにフラグを設定して、オブジェクトの再作成を防ぐことです。 しかし、これにより、getInstanceが常に正しいポインターを返すことを期待して書かれた、すでに複雑なコードの大規模な変更が必要になりました。 その結果、簡単な解決策がないことに気づき、私は今でも楽しんでいるloners(1つを残す)をほぼ完全に根こそぎにしました。
結論
シングルトンクラスのライフタイムの管理に関連する問題は、しばしば提起され、議論されます。 問題の根本はこのパターンの本質の誤解にあるように思えます。 孤独な人は一人でなければなりません 。 そして、それはすべてを解決します。 はい。結果には、エンティティ間の依存関係を識別する、より徹底的な設計が必要です。 しかし、それは価値があります。 最終的にはアプリケーション管理の単一のポイントが判明するため、インターフェイスとロジックを明確にします。
もちろん、このアプローチが受け入れられないアプリケーションは確かにあります。 しかし、これらは例外であり、例外と同様に扱う必要があります。 アプリケーションが1つの場合、クラスは作成と破棄の点で完全に独立している必要があります。
たとえば、キーボード、ディスプレイ、およびログを使用した古典的な例[1]。 これらのクラスは、グローバルアプリケーションが所有できます。 名前はボロボロですが、ここにその利点があります
- 確実 。 Application :: freeを呼び出した後、ゾンビフェニックスを恐れることはできません。開発者が意図した順序ですべてが正確に破壊されます。 ログは、他のすべてのオブジェクトを削除した後、delete mLogオペレーターへの最後の単純な呼び出しによって削除されます。
- シンプルさと移植性 。 言語の繊細さを使用する(および知識さえ)必要はありません。 このアーキテクチャは、主要なオブジェクト指向言語に自由に適応します。 さらに、シンプルなソリューションではエラーが発生しにくく、スタッフの資格要件も低くなります。 言い換えれば、それらは複雑なものよりも安くて優れている[3]
- テスト容易性 。 単一クラスの破壊を計画するためにatexit [1,2]のようなトリックを使用すると、完全にシャットダウンせずにアプリケーションのグローバル状態をリセットすることができなくなります。 その結果、たとえば、2つの独立したテストを実行するには、毎回プログラムを再起動する必要があります。
唯一の問題は、これらのクラスの作成/コピーが制御されないままになる場合があります。 そのため、理論的には、プログラマはキーボードコントロールオブジェクトをアプリケーションから取得する必要があることに気付かず、独自のインスタンスを作成できます。 このようなイベントの発生は、不要なコンストラクタをすべてクローズすることで簡単に抑制できます。 これにより、「手動」作成が不可能になります。 所有者クラスがキーボードオブジェクトをインスタンス化できるように、単にそれをフレンドとして宣言します。
class Keyboard { private: Keyboard(); friend class Application; };
このような実装は、重要な情報の役割を果たします。 ドキュメントがなくても、Keyboardクラスのインターフェイスを調べるだけで、開発者はインスタンスのApplicationにアクセスする必要があることを理解できます。
ほとんどの場合、単一のクラスをより優れたソリューションに置き換えることができます。 少なくとも私の場合、過去1年間、より詳細な分析を行って別のシングルトンを作成しようとする試みは、常に、計画されたアーキテクチャについてもう一度考えようとする怠inessと不本意の現れでした。
[1] Alexandrescu A. C ++のモダンデザイン:一般化されたプログラミングと応用デザインパターン。
[2] atexit
[3] キッス