目次
5.Singleton モード (オブジェクトは 1 つしか作成できません)
1. コピーできないクラス
クラスがコピーされないようにするには、コピーコンストラクタと代入演算子のオーバーロード関数をクラスから呼び出せないようにするか、クラスのコピーコンストラクタと代入演算子のオーバーロード関数を直接privateに設定するか、C++11を使用する必要があります。これらの 2 つの機能は削除できます
class Test
{
public:
Test() {}
//...
private:
//C++98
Test(const Test&);
Test& operator=(const Test&);
//C++11
//Test(const Test&) = delete;
//Test& operator=(const Test&) = delete;
};
2. オブジェクトはヒープ上にしか作成できない
方法1
オブジェクトはヒープ上にのみ作成できます。つまり、オブジェクトは次のように new 演算子を介してのみ作成できます。
- コンストラクターをプライベートとして設定して、コンストラクターへの外部直接呼び出しがスタック上にオブジェクトを作成するのを防ぎます
- オブジェクトを外部に取得するための静的インターフェースを提供し、ヒープ上にオブジェクトを作成して返す
- コピー コンストラクターは宣言されるだけで実装されず、プライベート アクセス許可として設定され、外部呼び出しがスタック上にオブジェクトを作成するのを防ぎます。
#include <iostream>
using namespace std;
class HeapOnly
{
public:
//提供公有的获取对象的静态方法
static HeapOnly* CreateObj(){
return new HeapOnly;
}
private:
HeapOnly() :_data(0) {};
HeapOnly(const HeapOnly& tmp) = delete;
private:
int _data;
};
int main()
{
HeapOnly* obj = HeapOnly::CreateObj();
delete obj;
return 0;
}
方法 2
デストラクタは非公開にすることができ、デストラクタを介してオブジェクト リソースを解放することはできません。つまり、強制的にヒープ上にオブジェクトを作成し、delete 演算子を使用してオブジェクト リソースを解放することしかできません。
#include <iostream>
using namespace std;
class HeapOnly
{
public:
void Delete() { delete this; }
private:
~HeapOnly() {}
private:
int _data;
};
int main()
{
HeapOnly* p = new HeapOnly;
p->Delete();
return 0;
}
3.オブジェクトはスタック上でのみ作成できます
- コンストラクターをプライベートとして設定して、ヒープ上にオブジェクトを作成するためのコンストラクターへの外部直接呼び出しを防ぎます
- オブジェクトを外部に取得するための静的インターフェースを提供し、スタック領域にオブジェクトを作成して返す
- operator new() 関数と operator delete() 関数をシールドします (コピー構築を呼び出してヒープ領域にオブジェクトを作成するのを防ぐため)
#include <iostream>
using namespace std;
class StackOnly
{
public:
static StackOnly CreateObj() {
return StackOnly();
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly():_data(0) {};
private:
int _data;
};
int main()
{
StackOnly so = StackOnly::CreateObj();
static StackOnly s1(so);//无法完全实现要求
return 0;
}
しかし、この方法には欠点があります。データ セグメントにオブジェクトを作成するためのコピー コンストラクターへの外部呼び出しを防ぐことはできません。
ただし、CreateObj() 関数はローカル オブジェクトを作成し、ローカル オブジェクトを返すプロセスでコピー コンストラクターを呼び出す必要があるため、コンストラクターをプライベートとして設定することはできず、コピー コンストラクターを =delete によって削除することはできません。
4. 継承できないクラス
方法 1 (C++98)
サブクラスのコンストラクターが呼び出されると、親クラスのメンバーの一部を初期化するために親クラスのコンストラクターを呼び出す必要があるため、このクラスのコンストラクターをプライベートとして設定するだけですが、親クラスのプライベートメンバーはサブクラスでは表示されない はい、サブクラス オブジェクトを作成するときに、サブクラスは親クラスのコンストラクターを呼び出して親クラスのメンバーを初期化できないため、サブクラスはクラスが継承された後にオブジェクトを作成できません
class Perent
{
public:
static Perent CreatePerentObj() {
return Perent();
}
private:
Perent() {};
};
方法 2 (C++11)
C++98 のこのメソッドは完全ではありません。このクラスは引き続き継承できますが (コンパイラはエラーを報告しません)、継承後にオブジェクトをインスタンス化することはできません。そこで C++11 では final キーワードが用意されている. final で修飾されたクラスを final クラスと呼ぶ. final クラスは継承できない. 継承後にオブジェクトを作成しなくてもコンパイルエラーになる.
class NonInherit final
{
//...
};
5.Singleton モード (オブジェクトは 1 つしか作成できません)
5.1 コンセプト
- シングルトン パターンはデザイン パターンです (デザイン パターン). デザイン パターンは、繰り返し使用され、ほとんどの人に知られ、カタログ化され、コード化されたコード設計経験の要約です。デザインパターンを使用する目的は、コードを再利用可能にし、コードを他の人が理解しやすくし、コード信頼性プログラムの再利用性を確保することです。
- シングルトン モードとは、クラスが 1 つのオブジェクトしか作成できないことを意味します。このモードでは、システム内にクラスのインスタンスが 1 つだけ存在することが保証され、それにアクセスするためのグローバル アクセス ポイントが提供されます。このインスタンスは、すべてのプログラム モジュールによって共有されます。
- たとえば、サーバー プログラムでは、サーバーの構成情報がファイルに格納され、これらの構成データがシングルトン オブジェクトによって読み込まれ、サービス プロセス内の他のオブジェクトがこのシングルトン オブジェクトを介して構成情報を取得します。複雑な環境での構成管理を簡素化
- シングルトン モードを実装するには、ハングリーマン モードとレイジーマン モードの 2 つの方法があります。
5.2 ハングリーマンモード
- コンストラクターをプライベートにし、コピー コンストラクターと代入演算子のオーバーロードされた関数をプライベートまたは削除して、オブジェクトの外部作成またはコピーを防ぎます。
- シングルトン オブジェクトへの静的ポインターを提供し、プログラム エントリの前にシングルトン オブジェクトの初期化を完了します。
- シングルトン オブジェクトのグローバル アクセス ポイントを提供します
class SingleTon
{
public:
static SingleTon* GetInstance() {//全局访问点
return _inst;
}
private:
SingleTon() {};//构造函数私有
//防拷贝
SingleTon(const SingleTon&) = delete;
SingleTon& operator=(const SingleTon&) = delete;
static SingleTon* _inst;//指向单例对象的static指针
};
SingleTon* SingleTon::_inst = new SingleTon;//在程序入口前完成单例对象的初始化
スレッドの安全性に関する問題:
- ハングリー モードでは、プログラムがメイン関数を実行する前にシングルトン オブジェクトの作成が完了します. main() 関数の前にマルチスレッド化がないため、ハングリー モードでのシングルトン オブジェクトの作成プロセスはスレッドセーフです.
- このシングルトン オブジェクトへの後続のマルチスレッド アクセスはすべて、GetInstance() 関数を呼び出して取得する必要があります。これは読み取り操作であるため、この取得プロセスをロックする必要はありません。
5.3 レイジーパターン
- コンストラクターをプライベートにし、コピー コンストラクターと代入演算子のオーバーロードされた関数をプライベートまたは削除して、オブジェクトの外部作成またはコピーを防ぎます。
- シングルトン オブジェクトへの静的ポインターを提供し、プログラム エントリの前に空に初期化します。
- シングルトン オブジェクトを取得するためのグローバル アクセス ポイントを提供し、グローバル アクセス ポイントへの初回アクセス時にシングルトン オブジェクトの初期化を完了する
#include <thread>
#include <mutex>
using namespace std;
class SingleTon
{
public:
static SingleTon* GetInstance() {//全局访问点
if (nullptr == _inst)
{
unique_lock<mutex>(_mtx);
if (nullptr == _inst) {
_inst = new SingleTon;
}
}
}
private:
SingleTon() {};//构造函数私有
//防拷贝
SingleTon(const SingleTon&) = delete;
SingleTon& operator=(const SingleTon&) = delete;
static SingleTon* _inst;//指向单例对象的static指针
static mutex _mtx;
};
SingleTon* SingleTon::_inst = nullptr;
mutex SingleTon::_mtx;//定义,在程序入口前完成初始化
スレッドの安全性に関する問題:
- 遅延モードは、プログラムの実行前にシングルトン オブジェクトを作成しませんが、スレッドがこのシングルトン オブジェクトを使用する必要があるまで待ってから作成します。つまり、GetInstance() 関数は、最初に呼び出されたときにシングルトン オブジェクトを作成します。
- そのため、シングルトン オブジェクトを取得するために GetInstance() 関数を呼び出す場合、静的ポインタが空であるかどうかを確認する必要があります.空の場合は、シングルトン オブジェクトが作成されていないことを意味します.このとき、シングルトン オブジェクトは、最初に作成する必要があり、その後シングルトン オブジェクトが返されます
- GetInstance() 関数が初めて呼び出されるとき、静的ポインタに書き込む必要があります. 複数のスレッドが同時に GetInstance() 関数を呼び出す可能性があるため、このプロセスはスレッドセーフではありません.保護されているため、この時点で複数のスレッドがそれぞれオブジェクトを作成します
ダブルチェックロック:
- GetInstance() 関数でシングルトン オブジェクトを作成するプロセスを保護するには、本質的にミューテックスの導入が必要です. ロックする最も簡単な方法は、if 判定の前にロックし、if ステートメント全体の後にロックを解除することです
- しかし実際には、最初に GetInstance 関数が呼び出されるだけです. シングルトン オブジェクトを作成する場合、保護のためにミューテックスが必要です. シングルトン オブジェクトを取得するための以降の GetInstance 関数の呼び出しは、読み取り操作のみであり、その必要はありません。保護のためにミューテックスを使用します。
- ロックとロック解除操作が if ステートメントの前後にあるだけだと、作成されたシングルトン オブジェクトを取得するために GetInstance() 関数が呼び出されると、無意味なロック操作とロック解除操作が大量に実行され、スレッドが継続的に切断されます。 in と out 、これはプログラムの効率に影響します
- このような初回のみロックする必要があるシーンでは、ダブルチェックを使用してロックすることができます. ダブルチェックは、現在のロックの外で別の if 判定を行い、静的ポインタが空であるかどうかを判定するロック解除を行います.
- その後、GetInstance() 関数を呼び出して作成されたシングルトン オブジェクトを取得すると、外側のレイヤーで新しく追加された if 判定が有効になり、その後の無意味なロックおよびロック解除操作が回避されます。
- new 演算子が例外をスローし、ロックが解除されない可能性があるため、ロックするときは unique_lock を使用することをお勧めします。
その他の実装
- シングルトン クラスの GetInstance(0 関数で静的なシングルトン オブジェクトを定義し、戻ります
- この静的なシングルトン オブジェクトは、GetInstance() 関数が初めて呼び出されたときにのみ実際に定義されるため、これにより、一意のインスタンスがグローバルに 1 つだけ存在することも保証されます。
- ここでのシングルトン オブジェクトの定義プロセスはスレッド セーフです。これは、現在の C++ 標準が、静的変数のマルチスレッド初期化がデータ競合を引き起こさないことを保証しているためです。これは、アトミック操作と見なすことができます。
- プログラムがメイン関数を実行する前にローカル静的変数が初期化されず、GetInstance() 関数が初めて呼び出されたときに初期化されるため、このメソッドは遅延モードに属します。
このバージョンの怠け者には、次の主な欠点があります。
- シングルトン オブジェクトは static 領域で定義されるため、大きすぎるシングルトン オブジェクトはこのメソッドには適していません。
- シングルトン オブジェクトが静的領域に作成された後、アクティブに解放する方法はありません。
- このメソッドは、C++11 バージョン以降でのみ使用できます。
class SignleTon
{
public:
static SignleTon* GetInstance() {
static SignleTon inst;
return &inst;
}
private:
SignleTon() {}
SignleTon(const SignleTon&) = delete;
SignleTon& operator=(const SignleTon&) = delete;
};
5.4 ハングリーマンとレイジーマンの比較
- ハングリーマン モードの利点はシンプルさですが、欠点も明らかです。ハングリーマン モードでは、プログラムがメイン関数を実行する前にシングルトン オブジェクトが作成されます. シングルトン クラスのコンストラクタがさらに多くの作業を行うと、プログラムがメイン関数に長時間入ることができなくなります.外部からのプログラム。
- シングルトン オブジェクトを作成する必要がある複数のシングルトン クラスがあり、それらの初期化の間に特定の依存関係がある場合、たとえば、シングルトン オブジェクト A はシングルトン オブジェクト B の後に作成する必要がある場合、ハングリーマン モードでも問題が発生します。複数のシングルトン オブジェクトのうち、どのオブジェクトが最初に作成されるかは保証されません。
- 怠惰な人モードはハングリーマン モードの上記の欠点を非常によく解決できます。怠惰な人モードは最初からシングルトン オブジェクトの作成を完了しないため、プログラムがメイン関数に入ることができなくなることはありません。長い時間、怠け者モードの各ケース オブジェクトの作成順序は、各シングルトン クラスの GetInstance() 関数が最初に呼び出される順序によって決定されるため、制御可能です。
- レイジーマン モードの欠点は、コーディングがハングリーマン モードよりも複雑であり、シングルトン オブジェクトを作成するときにスレッド セーフの問題を考慮する必要があることです。
6. シングルトンオブジェクトのリリース
シングルトン オブジェクトが作成されると、プログラムの実行中に使用される可能性があるため、シングルトン オブジェクトの解放は無視でき、プログラムが正常に終了すると、リソースはオペレーティング システムに自動的に返されます。
シングルトン オブジェクトのリリースを検討するには、次の 2 つの方法を参照できます。
1. シングルトン クラスに DelInstance() 関数を記述し、この関数でシングルトン オブジェクトを解放する シングルトン オブジェクトが不要になったら、積極的に DelInstance() を呼び出してシングルトン オブジェクトを解放できます。
static void DelInstance()
{
unique_lock<mutex>(_mtx);
if (_inst != nullptr)
{
delete _inst;
_inst = nullptr;
}
}
2. シングルトン クラスに埋め込みガベージ コレクション クラスを実装し、ガベージ コレクション クラスのデストラクタでシングルトン オブジェクトの解放を完了します。singleton クラスに static ガベージ コレクション クラス オブジェクトを定義し、オブジェクトがリサイクルされるとデストラクタが呼び出され、この時点で singleton オブジェクトが解放されます。
class CGarbo//垃圾回收类
{
public:
~CGarbo()
{
if (_inst != nullptr) {
delete _inst;
_inst = nullptr;
}
}
};