編集:別の質問から、シングルトンに関する多くの質問/回答へのリンクがある回答を提供しました: シングルトンに関する詳細はこちら:
だから私はスレッドを読んだ シングルトン:良いデザインか松葉杖?
そして、議論は依然として激怒しています。
私はシングルトンをデザインパターン(良い点と悪い点)として見ています。
シングルトンの問題はパターンではなく、ユーザーです(皆さんごめんなさい)。みんなと彼らの父親は、自分が正しいものを実装できると考えています(そして、私が行った多くのインタビューから、ほとんどの人はできません)。また、誰もが正しいシングルトンを実装できると考えているため、パターンを乱用し、不適切な状況で使用します(グローバル変数をシングルトンに置き換えます!)。
したがって、答えが必要な主な質問は次のとおりです。
この記事に対する私の希望は、Singletonを正しく使用するタイミング(および方法)の信頼できるソースを(複数のサイトをグーグル検索するのではなく)1か所で収集できることです。また、適切なのは、アンチユーセージと一般的な不良実装のリストで、それらが機能しない理由と、優れた実装の場合の弱点を説明することです。
だから、ボールを転がす:
手を挙げて、これが私が使用するものであるが、おそらく問題があると言います。
私は彼の著書「Effective C++」で主題を扱う「Scott Myers」が好きです
シングルトンを使用するのに適した状況(多くはない):
- ロギングフレームワーク
- スレッドリサイクルプール
/*
* 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;
}
};
OK。いくつかの批判と他の実装をまとめましょう。
:-)
皆さんは間違っています。質問を読んでください。回答:
次の場合にシングルトンを使用します。
次の場合は、シングルトンを使用しないでください。
最高のシングルトンを作成する方法:
シングルトンを使用すると、2つの悪い特性を1つのクラスにまとめることができます。それはほとんどすべての点で間違っています。
シングルトンはあなたに与えます:
ナンバーワンは簡単です。グローバルは一般に悪いです。 本当に必要でない限り、オブジェクトをグローバルにアクセス可能にしないでください。
2つ目は理にかなっているように聞こえるかもしれませんが、考えてみましょう。既存のオブジェクトを参照する代わりに、誤って新しいオブジェクトを作成したのはいつですか?これにはC++のタグが付けられているため、その言語の例を使用してみましょう。よく間違って書きますか
std::ostream os;
os << "hello world\n";
書くつもりだったとき
std::cout << "hello world\n";
もちろん違います。この種のエラーは発生しないため、このエラーに対する保護は必要ありません。もしそうなら、正しい反応は家に帰って12-20時間寝て、気分が良くなることを願っています。
オブジェクトが1つだけ必要な場合は、1つのインスタンスを作成するだけです。 1つのオブジェクトをグローバルにアクセス可能にする必要がある場合は、それをグローバルにします。しかし、それはそれの他のインスタンスを作成することが不可能であるべきだという意味ではありません。
「1つのインスタンスのみが可能」という制約は、起こりそうなバグから私たちを本当に保護しません。しかし、それはdoesであり、コードのリファクタリングと保守が非常に難しくなります。多くの場合、laterに複数のインスタンスが必要であることがわかります。私たちdoには複数のデータベースがあり、私たちdoには複数のデータベースがあります設定オブジェクトには、いくつかのロガーが必要です。単体テストでは、一般的な例を挙げるために、テストごとにこれらのオブジェクトを作成および再作成できるようにする必要があります。
したがって、シングルトンは、bothが提供する特性が必要な場合にのみ使用する必要があります:if weneedグローバルアクセス(グローバルは一般的に推奨されないため、まれです)and weneedto everクラスのインスタンスを複数作成しないようにします(デザインの問題のように聞こえます)。これを確認できる唯一の理由は、2つのインスタンスを作成するとアプリケーションの状態が破損する場合です。おそらく、クラスに多数の静的メンバーまたは同様の愚かさが含まれているためです。その場合、明白な答えはそのクラスを修正することです。唯一のインスタンスに依存するべきではありません。
オブジェクトへのグローバルアクセスが必要な場合は、std::cout
のようにグローバルにします。ただし、作成できるインスタンスの数を制限しないでください。
クラスのインスタンスの数を絶対に1つだけに制限する必要があり、2番目のインスタンスの作成を安全に処理する方法がない場合は、それを強制します。ただし、グローバルにアクセス可能にしないでください。
両方の特性が必要な場合は、1)それをシングルトンにし、2)そのようなケースを想像するのに苦労しているため、必要なものを教えてください。
シングルトンの問題は、その実装ではありません。それは2つの異なる概念を融合していることであり、どちらも明らかに望ましいものではありません。
1)シングルトンは、オブジェクトへのグローバルアクセスメカニズムを提供します。初期化順序が明確に定義されていない言語では、スレッドセーフやスレッド信頼性がわずかに高いかもしれませんが、この使用法は依然としてグローバル変数と同等です。それはいくつかの厄介な構文(g_fooの代わりにfoo :: get_instance()など)で装飾されたグローバル変数ですが、まったく同じ目的(プログラム全体でアクセス可能な単一のオブジェクト)を提供し、まったく同じ欠点があります。
2)シングルトンは、クラスの複数のインスタンス化を防ぎます。 IMEでは、この種の機能をクラスに組み込むことはまれです。通常、これははるかにコンテキスト的なものです。 1つだけと見なされているものの多くは、実際にはたまたま1つだけです。 IMOのより適切な解決策は、インスタンスを1つだけ作成することです(複数のインスタンスが必要であることに気付くまで)。
パターンに関する1つのこと:一般化しない。役に立つ場合と失敗する場合のすべてのケースがあります。
testコードを実行する必要がある場合、シングルトンは厄介な場合があります。通常、クラスの1つのインスタンスにこだわっており、コンストラクターでドアを開くか、何らかの方法で状態をリセットするかを選択できます。
他の問題は、実際にはシングルトンが偽装のグローバル変数にすぎないことです。プログラム上でグローバルな共有状態が多すぎると、状況は元に戻りやすくなります。
依存関係の追跡が難しくなる場合があります。すべてがシングルトンに依存している場合、それを変更したり、2つに分割したりすることは難しくなります。これも柔軟性を妨げます。いくつかのDependency Injectionフレームワークを調査して、この問題を軽減してください。
シングルトンを使用すると、基本的に、複雑なグローバル変数を持つことが困難または不可能になる言語の複雑なグローバル状態を使用できます。
特にJavaは、すべてをクラス内に含める必要があるため、グローバル変数の代わりにシングルトンを使用します。グローバル変数に最も近いのはpublic static変数で、import static
でグローバルであるかのように使用できます
C++にはグローバル変数がありますが、グローバルクラス変数のコンストラクターが呼び出される順序は未定義です。そのため、シングルトンを使用すると、グローバル変数の作成をその変数が初めて必要になるまで延期できます。
代わりにモジュール内でグローバル変数を使用できるため、PythonやRubyなどの言語はシングルトンをほとんど使用しません。
それでは、いつシングルトンを使用するのが良い/悪いのですか?グローバル変数を使用するのが良いか悪いかは、ほぼ正確です。
- シングルトンを正しく実装する方法
私が言及したことは一度もありませんが、前の仕事で出くわした問題が1つあります。 DLL間で共有されるC++シングルトンがあり、クラスの単一インスタンスが機能しないことを保証する通常のメカニズムがありました。問題は、各DLLがEXEとともに独自の静的変数セットを取得することです。 get_instance関数がインラインまたは静的ライブラリの一部である場合、各DLLには「シングルトン」の独自のコピーが作成されます。
解決策は、シングルトンコードが1つのDLLまたはEXEでのみ定義されていることを確認するか、これらのプロパティを使用してインスタンスをパーセルするシングルトンマネージャーを作成することです。
Modern C++ Design by Alexandrescuには、スレッドセーフで継承可能なジェネリックシングルトンがあります。
私の2p-worthについては、シングルトンのライフタイムを定義することが重要だと思います(使用することが絶対に必要な場合)。私は通常、静的なget()
関数に何もインスタンス化させず、セットアップと破壊をメインアプリケーションの専用セクションに任せます。これは、シングルトン間の依存関係を強調表示するのに役立ちますが、上記で強調したように、可能であればそれらを回避するのが最善です。
最初の例はスレッドセーフではありません。2つのスレッドが同時にgetInstanceを呼び出すと、その静的はPITAになります。何らかの形式のミューテックスが役立ちます。
他の人が指摘したように、シングルトンの主な欠点には、シングルトンを拡張できないことや、複数のインスタンスをインスタンス化する力が失われることなどがあります。テスト目的で。
シングルトンのいくつかの有用な側面:
ただし、これらの利点を得るためにシングルトンを使用する必要はありません。作業を行う通常のオブジェクトを作成して、ファクトリー(別のオブジェクト)を介してユーザーにアクセスさせることができます。工場は、必要に応じてインスタンス化と再利用などのみを心配することができます。また、具体的なクラスではなくインターフェイスにプログラミングする場合、ファクトリは戦略を使用できます。つまり、インターフェイスのさまざまな実装を切り替えられます。
最後に、工場はSpringなどの依存性注入技術に適しています。
シングルトンでは1つのインスタンスしか作成できないため、インスタンスの複製を効果的に制御します。たとえば、ルックアップの複数のインスタンス(モールスルックアップマップなど)が必要ないため、シングルトンクラスでラップするのが適切です。また、クラスのインスタンスが1つだけであるからといって、そのインスタンスへの参照の数も制限されるわけではありません。インスタンスへの呼び出しをキューに入れて(スレッドの問題を回避するため)、必要な変更を行うことができます。はい、シングルトンの一般的な形式はグローバルに公開されています。デザインを変更して、アクセスが制限されたシングルトンを作成できます。私はこれを前に疲れませんでしたが、それが可能であることを確かに知っています。そして、シングルトンパターンは完全に悪であるとコメントしたすべての人に、これを知っておく必要があります。
シングルトンは、初期化してオブジェクト化するときに多くのコードを実行する場合に便利です。たとえば、永続オブジェクトをセットアップするときにiBatisを使用する場合、コードに到達する前に、すべての構成を読み取り、マップを解析し、すべてが正しいことを確認する必要があります。
これを毎回行うと、パフォーマンスが大幅に低下します。シングルトンでそれを使用すると、そのヒットを1回取得し、その後のすべての呼び出しで実行する必要はありません。
大部分の人は、グローバル変数の使用について気分を良くしようとするときにシングルトンを使用します。正当な用途はありますが、ほとんどの場合、人々がそれらを使用するのは、インスタンスが1つしか存在できないという事実は、グローバルにアクセス可能であるという事実と比べると些細な事実です。
シングルトンの本当の没落は、継承を破ることです。シングルトンが参照されているコードにアクセスできない限り、新しいクラスを派生して拡張機能を提供することはできません。そのため、Singletonはコードを密結合(戦略パターンによって修正可能...依存性注入とも呼ばれます)するだけでなく、リビジョン(共有ライブラリ)からコードのセクションを閉じることも防ぎます。
そのため、ロガーやスレッドプールの例も無効であり、ストラテジーに置き換える必要があります。
しかし、シングルトンのようなものが必要なときは、しばしば Schwarz Counter を使用してインスタンス化します。
面接テストとしてシングルトンを使用します。
開発者にいくつかのデザインパターンに名前を付けるように依頼するときに、名前を付けることができるのがシングルトンだけであれば、それらは雇われません。
以下は、デストラクタ自体のメモリの割り当てを解除して、スレッドセーフなシングルトンパターンを実装するためのより良いアプローチです。しかし、プログラムが終了するとシングルトンインスタンスが自動的に破棄されるため、デストラクタはオプションである必要があると思います。
#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;
}
シングルトンクラスを使用する必要がある状況については、プログラムの実行中にインスタンスの状態を維持したい場合、ファイルの1つのインスタンスのみが必要なアプリケーションの実行ログに書き込む場合使用される...など。上記のコードで最適化を提案できる人がいれば、かなりの量になるでしょう。
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つのことに使用しました-dlopenでロードされたライブラリを示すシングルトンテーブル-ロガーがサブスクライブでき、メッセージを送信できるメッセージハンドラー。特にシグナルハンドラに必要です。
あなたがシングルトンを作成し、それを使用する人であれば、シングルトンとして作成しないでください(シングルトンにすることなくオブジェクトの特異性を制御できるため意味がありません)が、開発者がライブラリを使用して、ユーザーにオブジェクトを1つだけ提供したい場合(この場合、シングルトンを作成したのはあなたですが、あなたはユーザーではありません)。
シングルトンはオブジェクトなので、それらをオブジェクトとして使用し、多くの人はそれを返すメソッドを呼び出すことでシングルトンに直接アクセスしますが、オブジェクトがシングルトンであることをコードに知らせるため、これは有害です、シングルトンをオブジェクトとして使用することを好みます、私はそれらを渡しますコンストラクタを介して、私はそれらを通常のオブジェクトとして使用します。その方法では、コードはこれらのオブジェクトがシングルトンであるかどうかを知らず、依存関係をより明確にし、リファクタリングに少し役立ちます...
アンチユーセージ:
シングルトンの過度の使用に関する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();
}
}
}
}
このパターンは dotfactory.com で見つけることができます。
シングルトンがグローバルである必要がある理由はまだわかりません。
クラス内にデータベースをプライベート定数静的変数として隠し、データベースをユーザーに公開せずにデータベースを利用するクラス関数を作成するシングルトンを作成しました。
この機能が悪い理由はわかりません。