シングルトン:どのように使用すればよいですか
-
01-07-2019 - |
質問
編集:別の質問から、シングルトンに関する多くの質問/回答へのリンクを含む回答を提供しました。 シングルトンの詳細については、こちらをご覧ください。
それでスレッドを読みました シングルトン:良いデザインか松葉杖か?
そして議論は今も激化している。
私はシングルトンをデザイン パターン (良い点も悪い点も含めて) として捉えています。
シングルトンの問題はパターンではなく、むしろユーザーにあります (皆さん、ごめんなさい)。誰もが、そしてその父親は、それを正しく実装できると考えています (そして、私が行った多くのインタビューによると、ほとんどの人は実装できません)。また、誰もが正しいシングルトンを実装できると考えているため、パターンを悪用し、不適切な状況でパターンを使用します (グローバル変数をシングルトンに置き換えます!)。
したがって、答える必要がある主な質問は次のとおりです。
- シングルトンを使用する必要がある場合
- シングルトンを正しく実装するにはどうすればよいですか
この記事に対する私の願いは、シングルトンをいつ (そしてどのように) 正しく使用するかについての信頼できる情報源を (複数のサイトで Google で検索するのではなく) 1 か所にまとめることです。また、アンチユースのリストと、それらが機能しない理由を説明する一般的な悪い実装と、優れた実装の場合の弱点を説明することも適切です。
それでは、ボールを転がしてみましょう。
私は手を上げて、これは私が使っているものですが、おそらく問題があると言います。
私は、「Scott Myers」の著書「Effective C++」でのテーマの扱いが好きです。
シングルトンを使用するのに適した状況 (それほど多くはありません):
- ロギングフレームワーク
- スレッドリサイクルプール
/*
* C++ Singleton
* Limitation: Single Threaded Design
* See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
* For problems associated with locking in multi threaded applications
*
* Limitation:
* If you use this Singleton (A) within a destructor of another Singleton (B)
* This Singleton (A) must be fully constructed before the constructor of (B)
* is called.
*/
class MySingleton
{
private:
// Private Constructor
MySingleton();
// Stop the compiler generating methods of copy the object
MySingleton(MySingleton const& copy); // Not Implemented
MySingleton& operator=(MySingleton const& copy); // Not Implemented
public:
static MySingleton& getInstance()
{
// The only instance
// Guaranteed to be lazy initialized
// Guaranteed that it will be destroyed correctly
static MySingleton instance;
return instance;
}
};
わかりました。批判やその他の実装についてもまとめてみましょう。
:-)
解決
皆さんは間違っています。質問を読んでください。答え:
次の場合にはシングルトンを使用します。
- システム内には、同じタイプのオブジェクトが 1 つだけ必要です
次の場合はシングルトンを使用しないでください。
- メモリを節約したい
- 何か新しいことを試してみたい
- 自分がどれだけ知っているかを誇示したい
- 他のみんながやってるから(参照) カーゴカルトプログラマー ウィキペディアにあります)
- ユーザーインターフェイスウィジェット内
- それはキャッシュであるはずです
- 文字列で
- セッション中
- 一日中行けるよ
最適なシングルトンを作成する方法:
- 小さいほど良いです。私はミニマリストです
- スレッドセーフであることを確認してください
- 決して null にならないようにしてください
- 作成が一度だけであることを確認してください
- 遅延またはシステム初期化?ご要望に応じて
- 場合によっては、OS または JVM がシングルトンを作成することがあります (例:Java ではすべてのクラス定義がシングルトンです)
- デストラクターを提供するか、何らかの方法でリソースを破棄する方法を見つけます。
- メモリの使用量が少ない
他のヒント
シングルトンを使用すると、2 つの悪い特性を 1 つのクラスに組み合わせることができます。それはほとんどすべての点で間違っています。
シングルトンでは次のことが得られます。
- オブジェクトへのグローバル アクセス、および
- このタイプのオブジェクトが 1 つだけであるという保証 いつでも作成できます
1番は簡単です。グローバルは一般的に悪いです。次の場合を除き、オブジェクトをグローバルにアクセスできるようにすべきではありません。 本当に それが必要。
2 番目は理にかなっているように聞こえるかもしれませんが、考えてみましょう。最後に**誤って*、既存のオブジェクトを参照する代わりに新しいオブジェクトを作成したのはいつですか?これには C++ というタグが付いているので、その言語の例を使用してみましょう。よく誤って書いてしまいますか
std::ostream os;
os << "hello world\n";
書こうと思ったとき
std::cout << "hello world\n";
もちろん違います。この種のエラーは実際には発生しないため、このエラーに対する保護は必要ありません。そのような場合は、家に帰って 12 ~ 20 時間寝て、気分が良くなることを祈るのが正しい対応です。
必要なオブジェクトが 1 つだけの場合は、インスタンスを 1 つ作成するだけです。1 つのオブジェクトをグローバルにアクセスできるようにする必要がある場合は、それをグローバルにします。しかし、それは、他のインスタンスを作成することが不可能であるべきだという意味ではありません。
「可能なインスタンスは 1 つだけ」という制約では、発生する可能性のあるバグから実際に身を守ることはできません。しかし、それは する コードのリファクタリングと保守が非常に困難になります。なぜなら、かなりの頻度で私たちは発見するからです 後で 複数のインスタンスが必要だったということです。私たちは する 複数のデータベースがあります。 する 複数の構成オブジェクトがある場合は、複数のロガーが必要です。一般的な例を挙げると、単体テストではテストごとにこれらのオブジェクトを作成および再作成できるようにしたい場合があります。
したがって、必要な場合にのみシングルトンを使用する必要があります。 両方 それが提供する特徴:もし私達 必要 グローバル アクセス (グローバルは一般に推奨されないため、これはまれです) そして 私たちは 必要 誰かがそれを防ぐために これまで クラスの複数のインスタンスを作成する (これは設計上の問題のように思えます)。これについて私が確認できる唯一の理由は、2 つのインスタンスを作成するとアプリケーションの状態が破損するかどうかです。おそらく、クラスに多数の静的メンバーまたは同様の愚かな要素が含まれているためです。その場合、明らかな答えは、そのクラスを修正することです。唯一のインスタンスであることに依存すべきではありません。
オブジェクトへのグローバル アクセスが必要な場合は、次のようにオブジェクトをグローバルにします。 std::cout
. 。ただし、作成できるインスタンスの数を制限しないでください。
クラスのインスタンス数を確実に 1 つに制限する必要があり、2 番目のインスタンスの作成を安全に処理する方法がない場合は、それを強制してください。ただし、グローバルにアクセスできるようにしないでください。
両方の特性が必要な場合は、1) それをシングルトンにし、2) それが何に必要なのかを教えてください。そのようなケースは想像するのが難しいからです。
シングルトンの問題はその実装ではありません。それは、これらが 2 つの異なる概念を混同しているということであり、どちらも明らかに望ましいものではありません。
1) シングルトンは、オブジェクトへのグローバル アクセス メカニズムを提供します。初期化順序が明確に定義されていない言語では、スレッドセーフ性がわずかに向上したり、信頼性がわずかに向上したりする可能性がありますが、この使用法は依然としてグローバル変数と道徳的に同等です。これは、厄介な構文 (g_foo の代わりに foo::get_instance() など) で装飾されたグローバル変数ですが、まったく同じ目的 (プログラム全体からアクセスできる単一のオブジェクト) を果たし、まったく同じ欠点があります。
2) シングルトンは、クラスの複数のインスタンス化を防ぎます。IME さん、この種の機能をクラスに組み込む必要があるのはまれです。通常、これははるかに状況に応じたものです。唯一無二とみなされているものの多くは、実際にはたまたま唯一無二であるだけです。私の意見では、より適切な解決策は、複数のインスタンスが必要であることがわかるまで、インスタンスを 1 つだけ作成することです。
パターンに関する 1 つのこと: 一般化しないでください. 。役立つ場合も失敗する場合もすべてあります。
シングルトンは必要なときに厄介になる可能性があります テスト コード。通常、クラスのインスタンスは 1 つだけで済み、コンストラクターでドアを開けるか、状態をリセットするメソッドなどを選択できます。
もう 1 つの問題は、シングルトンが実際には単なる グローバル変数 変装した。プログラムに対してグローバルに共有された状態が多すぎると、状況が元に戻る傾向があることは誰もが知っています。
それは可能性があります 依存関係の追跡 もっと強く。すべてがシングルトンに依存している場合、シングルトンを変更したり、2 つに分割したりすることが難しくなります。一般的にはそれに行き詰まっています。これも柔軟性を妨げます。いくつか調べてください 依存関係の注入 この問題を軽減するためのフレームワーク。
シングルトンを使用すると、基本的に言語で複雑なグローバル状態を保持できるようになりますが、シングルトンを使用しないと、複雑なグローバル変数を持つことが困難または不可能になります。
特に Java では、すべてをクラス内に含める必要があるため、グローバル変数の代わりにシングルトンを使用します。グローバル変数に最も近いのはパブリック静的変数であり、グローバル変数であるかのように使用できます。 import static
C++ にはグローバル変数がありますが、グローバル クラス変数のコンストラクターが呼び出される順序は未定義です。そのため、シングルトンを使用すると、グローバル変数の作成を、その変数が初めて必要になるまで延期できます。
Python や Ruby などの言語では、代わりにモジュール内でグローバル変数を使用できるため、シングルトンはほとんど使用されません。
では、シングルトンを使用するのが良い場合と悪い場合は何でしょうか?グローバル変数を使用するのが良い場合と悪い場合は、ほぼ正確に一致します。
最新の C++ 設計 Alexandrescu による、スレッドセーフで継承可能なジェネリック シングルトンがあります。
私の 2p の価値については、シングルトンの有効期間を定義することが重要だと思います (シングルトンを使用する必要がある場合)。普段は静電気を起こさないのですが、 get()
この関数はあらゆるものをインスタンス化し、セットアップと破棄はメイン アプリケーションの専用セクションに任せます。これはシングルトン間の依存関係を強調するのに役立ちますが、上で強調したように、可能であれば依存関係を避けるのが最善です。
- シングルトンを正しく実装するにはどうすればよいですか
言及されたのを見たことがなかった問題が 1 つあります。これは、以前の仕事で遭遇した問題です。DLL 間で共有される C++ シングルトンがありましたが、クラスの単一インスタンスを保証する通常の仕組みは機能しませんでした。問題は、各 DLL が EXE とともに独自の静的変数のセットを取得することです。get_instance 関数がインラインまたは静的ライブラリの一部である場合、各 DLL には「シングルトン」の独自のコピーが作成されます。
解決策は、シングルトン コードが 1 つの DLL または EXE でのみ定義されていることを確認するか、それらのプロパティを備えたシングルトン マネージャーを作成してインスタンスを分割することです。
最初の例はスレッド セーフではありません。2 つのスレッドが同時に getInstance を呼び出すと、その静的変数は PITA になります。何らかの形式のミューテックスが役立つでしょう。
他の人が指摘しているように、シングルトンの主な欠点には、シングルトンを拡張できないことと、複数のインスタンスをインスタンス化する権限が失われることが含まれます。テスト目的のため。
シングルトンの便利な側面は次のとおりです。
- 遅延インスタンス化または事前インスタンス化
- セットアップや状態を必要とするオブジェクトに便利です
ただし、これらの利点を得るためにシングルトンを使用する必要はありません。作業を行う通常のオブジェクトを作成し、ユーザーがファクトリ (別個のオブジェクト) 経由でそれにアクセスできるようにします。工場では、インスタンスを 1 つだけ作成し、必要に応じてそれを再利用するなどの処理を行うことができます。また、具体的なクラスではなくインターフェイスに対してプログラムする場合、ファクトリは戦略を使用できます。インターフェースのさまざまな実装を切り替えたり、切り替えたりできます。
最後に、ファクトリは Spring などの依存性注入テクノロジに適しています。
シングルトンは、初期化およびオブジェクト実行時に大量のコードが実行される場合に便利です。たとえば、永続オブジェクトをセットアップするときに iBatis を使用する場合、すべての設定を読み取り、マップを解析し、すべてが正しいことを確認する必要があります。コードに到達する前に。
これを毎回実行すると、パフォーマンスが大幅に低下します。これをシングルトンで使用すると、そのヒットを 1 回受け取るだけで、後続のすべての呼び出しでそれを行う必要がなくなります。
シングルトンの本当の欠点は、継承が壊れることです。シングルトンが参照されるコードにアクセスできない限り、拡張機能を提供する新しいクラスを派生することはできません。したがって、シングルトンによってコードが密結合になります (戦略パターンによって修正可能です...)別名依存関係の注入)、コードのセクションをリビジョン (共有ライブラリ) から閉じることも防止します。
したがって、ロガーやスレッド プールの例も無効であり、Strategy に置き換える必要があります。
ほとんどの人は、グローバル変数を使用することに満足したいときにシングルトンを使用します。正当な使用方法はありますが、ほとんどの場合、人々が使用する場合、インスタンスが 1 つしか存在できないという事実は、グローバルにアクセスできるという事実に比べれば、些細な事実にすぎません。
シングルトンでは 1 つのインスタンスしか作成できないため、インスタンスのレプリケーションを効果的に制御します。たとえば、ルックアップの複数のインスタンスは必要ありません。モールス ルックアップ マップなどは、シングルトン クラスでラップするのが適切です。また、クラスのインスタンスが 1 つしかないからといって、そのインスタンスへの参照の数も制限されるわけではありません。(スレッドの問題を回避するために) インスタンスへの呼び出しをキューに入れて、必要な変更を加えることができます。はい、シングルトンの一般的な形式はグローバルに公開されており、設計を変更してよりアクセスが制限されたシングルトンを作成することができます。これまでこれにうんざりしたことはありませんでしたが、それが可能であることは確かに知っています。そして、シングルトン パターンは完全に悪であるとコメントしたすべての人は、次のことを知っておく必要があります。はい、それを適切に使用しない場合、または効果的な機能と予測可能な動作の範囲内で使用しない場合は悪です。一般化しないでください。
しかし、シングルトンのようなものが必要な場合、最終的には シュヴァルツカウンター それをインスタンス化します。
私は面接試験としてシングルトンを使用しています。
開発者にいくつかのデザイン パターンの名前を尋ねるとき、シングルトンしか名前を付けることができない場合、その開発者は採用されません。
以下は、デストラクター自体でメモリの割り当てを解除して、スレッド セーフなシングルトン パターンを実装するためのより良いアプローチです。ただし、シングルトンインスタンスはプログラム終了時に自動的に破棄されるため、デストラクターはオプションであるべきだと思います。
#include<iostream>
#include<mutex>
using namespace std;
std::mutex mtx;
class MySingleton{
private:
static MySingleton * singletonInstance;
MySingleton();
~MySingleton();
public:
static MySingleton* GetInstance();
MySingleton(const MySingleton&) = delete;
const MySingleton& operator=(const MySingleton&) = delete;
MySingleton(MySingleton&& other) noexcept = delete;
MySingleton& operator=(MySingleton&& other) noexcept = delete;
};
MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
delete singletonInstance;
};
MySingleton* MySingleton::GetInstance(){
if (singletonInstance == NULL){
std::lock_guard<std::mutex> lock(mtx);
if (singletonInstance == NULL)
singletonInstance = new MySingleton();
}
return singletonInstance;
}
Singletonクラスを使用する必要がある状況に関しては、ファイルの1つのインスタンスのみが必要なアプリケーションの実行ログに書き込みに関与している場合、プログラムの実行全体でインスタンスの状態を維持したい場合にできます。使用する....など。誰かが私の上記のコードの最適化を提案していただければ幸いです。
使用禁止:
過剰なシングルトンの使用に関する大きな問題の 1 つは、パターンによって代替実装の簡単な拡張や交換が妨げられることです。シングルトンが使用される場合、クラス名はハードコーディングされます。
これだと思います 最も堅牢なバージョン C# の場合:
using System;
using System.Collections;
using System.Threading;
namespace DoFactory.GangOfFour.Singleton.RealWorld
{
// MainApp test application
class MainApp
{
static void Main()
{
LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
// Same instance?
if (b1 == b2 && b2 == b3 && b3 == b4)
{
Console.WriteLine("Same instance\n");
}
// All are the same instance -- use b1 arbitrarily
// Load balance 15 server requests
for (int i = 0; i < 15; i++)
{
Console.WriteLine(b1.Server);
}
// Wait for user
Console.Read();
}
}
// "Singleton"
class LoadBalancer
{
private static LoadBalancer instance;
private ArrayList servers = new ArrayList();
private Random random = new Random();
// Lock synchronization object
private static object syncLock = new object();
// Constructor (protected)
protected LoadBalancer()
{
// List of available servers
servers.Add("ServerI");
servers.Add("ServerII");
servers.Add("ServerIII");
servers.Add("ServerIV");
servers.Add("ServerV");
}
public static LoadBalancer GetLoadBalancer()
{
// Support multithreaded applications through
// 'Double checked locking' pattern which (once
// the instance exists) avoids locking each
// time the method is invoked
if (instance == null)
{
lock (syncLock)
{
if (instance == null)
{
instance = new LoadBalancer();
}
}
}
return instance;
}
// Simple, but effective random load balancer
public string Server
{
get
{
int r = random.Next(servers.Count);
return servers[r].ToString();
}
}
}
}
ここにあります .NET に最適化されたバージョン:
using System;
using System.Collections;
namespace DoFactory.GangOfFour.Singleton.NETOptimized
{
// MainApp test application
class MainApp
{
static void Main()
{
LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
// Confirm these are the same instance
if (b1 == b2 && b2 == b3 && b3 == b4)
{
Console.WriteLine("Same instance\n");
}
// All are the same instance -- use b1 arbitrarily
// Load balance 15 requests for a server
for (int i = 0; i < 15; i++)
{
Console.WriteLine(b1.Server);
}
// Wait for user
Console.Read();
}
}
// Singleton
sealed class LoadBalancer
{
// Static members are lazily initialized.
// .NET guarantees thread safety for static initialization
private static readonly LoadBalancer instance =
new LoadBalancer();
private ArrayList servers = new ArrayList();
private Random random = new Random();
// Note: constructor is private.
private LoadBalancer()
{
// List of available servers
servers.Add("ServerI");
servers.Add("ServerII");
servers.Add("ServerIII");
servers.Add("ServerIV");
servers.Add("ServerV");
}
public static LoadBalancer GetLoadBalancer()
{
return instance;
}
// Simple, but effective load balancer
public string Server
{
get
{
int r = random.Next(servers.Count);
return servers[r].ToString();
}
}
}
}
このパターンは次の場所で見つけることができます ドットファクトリー.com.
Meyers のシングルトン パターンはほとんどの場合十分に機能しますが、機能する場合には、より優れたものを探すのは必ずしも有益ではありません。コンストラクターがスローせず、シングルトン間に依存関係がない限り。
シングルトンは、 グローバルにアクセス可能なオブジェクト (以降 GAO) ただし、すべての GAO がシングルトンであるわけではありません。
ロガー自体はシングルトンであってはなりませんが、ログメッセージが生成される場所とログが記録される場所または方法を分離するために、ログを記録する手段は理想的にはグローバルにアクセス可能である必要があります。
遅延読み込み/遅延評価は別の概念であり、シングルトンは通常それも実装します。これには多くの独自の問題が伴います。特にスレッドセーフ性と、当時は良いアイデアのように思えたものが、結局はそれほど素晴らしいものではなかったことが判明するような例外で失敗した場合の問題です。(文字列での COW 実装に少し似ています)。
これを念頭に置いて、GOA は次のように初期化できます。
namespace {
T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;
}
int main( int argc, char* argv[])
{
T1 t1(args1);
T2 t2(args2);
T3 t3(args3);
T4 t4(args4);
pt1 = &t1;
pt2 = &t2;
pt3 = &t3;
pt4 = &t4;
dostuff();
}
T1& getT1()
{
return *pt1;
}
T2& getT2()
{
return *pt2;
}
T3& getT3()
{
return *pt3;
}
T4& getT4()
{
return *pt4;
}
これほど大雑把に行う必要はありません。明らかに、オブジェクトを含むロードされたライブラリでは、オブジェクトの有効期間を管理するための他のメカニズムが必要になるでしょう。(ライブラリをロードするときに取得するオブジェクトにそれらを配置します)。
シングルトンを使用する場合についてはどうですか?私はそれらを2つのものに使用しました - ドロープンでロードされたライブラリを示すシングルトンテーブル - ロガーがサブスクライブできるメッセージハンドラーとメッセージを送信できることです。特にシグナル ハンドラーに必要です。
シングルトンがグローバルでなければならない理由がまだわかりません。
私はデータベースをプライベート定数静的変数としてクラス内に隠し、データベースをユーザーに公開せずにデータベースを利用するクラス関数を作成するシングルトンを作成するつもりでした。
なぜこの機能が悪いのかわかりません。
大量のメモリをカプセル化するクラスがある場合、これらは便利だと思います。たとえば、私が取り組んでいる最近のゲームには、連続したメモリの非常に大きな配列のコレクションを含むインフルエンス マップ クラスがあります。起動時にすべて割り当てられ、シャットダウン時にすべて解放されるようにしたいのですが、絶対にそのコピーは 1 つだけ必要です。また、さまざまな場所からアクセスする必要があります。この場合、シングルトン パターンが非常に便利だと思います。
他にも解決策はあると思いますが、これは非常に便利で実装が簡単だと思います。
あなたがシングルトンを作成し、それを使用する人である場合は、それをシングルトンとして作成しないでください (シングルトンにしなくてもオブジェクトの特異性を制御できるため、意味がありません)。ライブラリを作成し、ユーザーにオブジェクトを 1 つだけ提供したいとします (この場合、あなたはシングルトンの作成者ですが、ユーザーではありません)。
シングルトンはオブジェクトなので、オブジェクトとして使用します。多くの人は、シングルトンを返すメソッドを呼び出してシングルトンに直接アクセスしますが、オブジェクトがシングルトンであることをコードに認識させているため、これは有害です。私はシングルトンをオブジェクトとして使用することを好み、シングルトンを渡します。コンストラクターを介してそれらを通常のオブジェクトとして使用します。そうすることで、コードはこれらのオブジェクトがシングルトンであるかどうかを知りません。これにより、依存関係がより明確になり、リファクタリングに少し役立ちます...
デスクトップ アプリ (わかっていますが、これらを作成できるのはもう恐竜だけです!) では、比較的変更のないグローバル アプリケーション設定 (ユーザー言語、ヘルプ ファイルへのパス、ユーザー設定など) を取得するために不可欠です。そうでなければ、すべてのクラスとすべてのダイアログに反映する必要があります。 。
編集 - もちろん、これらは読み取り専用である必要があります。
別の実装
class Singleton
{
public:
static Singleton& Instance()
{
// lazy initialize
if (instance_ == NULL) instance_ = new Singleton();
return *instance_;
}
private:
Singleton() {};
static Singleton *instance_;
};