特別な要件を持つクラスを実装する
1 コピーできないクラス
C++98:
- コピー コンストラクターと代入演算子のオーバーロードは、宣言するだけで定義はしません(クラス外呼び出しを防ぐため)。
- アクセス許可をprivateに設定するだけです(メンバー関数の内部コピーを防ぐため)。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
C++11: 既定のメンバー関数に続いて=delete
、コンパイラは既定のメンバー関数を削除し、コンパイル中にエラーを報告できます。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
2 ヒープ上にしかオブジェクトを作成できないクラス
方法 1
- クラスのコンストラクターをプライベートとして宣言および実装します。
- クラスのコピー構築はプライベートとして宣言され、実装されていません。
- 静的メンバー関数を提供し、静的メンバー関数でヒープ オブジェクトの作成を完了します。
//方法1
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly() {}
//拷贝构造函数的写法两者选其一
HeapOnly(const HeapOnly&);//c98
HeapOnly(const HeapOnly&) = delete;//c11
};
//调用
HeapOnly* ho1 = HeapOnly::CreateObject();//可以
HeapOnly ho2;//err "HeapOnly::HeapOnly()" (已声明 所在行数:14) 不可访问
HeapOnly ho3(*ho);//err C98: "HeapOnly::HeapOnly(const HeapOnly &)" (已声明 所在行数:16) 不可访问 || C11: “HeapOnly::HeapOnly(const HeapOnly &)”: 尝试引用已删除的函数
方法 2 は、デストラクタをプライベートとして宣言および実装し、コピー コンストラクタをプライベートとして宣言し、実装しません。public destroy オブジェクト メソッドを提供します。
class HeapOnly
{
public:
HeapOnly() {}
void destory()
{
this->~HeapOnly();
}
private:
~HeapOnly(){}
//拷贝构造函数的写法两者选其一
HeapOnly(const HeapOnly&);//c98
//HeapOnly(const HeapOnly&) = delete;//c11
};
//调用
HeapOnly* ho1 = new HeapOnly;//可以
HeapOnly ho;//err 无法访问构造函数
スタック上でしかオブジェクトを作成できない 3 つのクラス
方法 1
- クラスのコンストラクターをプライベートとして宣言および実装します。
- 静的メンバー関数が用意されており、静的メンバー関数内でスタック オブジェクトの作成が完了します。
class StackOnly
{
public:
static StackOnly create()
{
return StackOnly();
}
private:
StackOnly(){}
};
//调用
StackOnly s1 = StackOnly::create();//可以
static StackOnly s2;//err "StackOnly::StackOnly()" (已声明 所在行数:13) 不可访问
static StackOnly s3 = StackOnly::create();//这个没办法封死
StackOnly s4;//全局变量err 无法访问构造函数
StackOnly s5 = new StackOnly;//err 无法访问构造函数
operator new と operator delete をオーバーロードし、両方に delete を追加すると、ヒープ上のオブジェクトの作成のみを無効にできますが、静的領域やグローバル領域などの変数の作成を無効にすることはできません。
4 継承できないクラス
C++98:
- クラスのコンストラクターをプライベートとして宣言および実装する; 派生クラスで基底クラスのコンストラクターを呼び出すことができない場合、それは継承できません
class NonInherit
{
public:
static NonInherit GetInstance() { return NonInherit(); }
private:
NonInherit() {}
};
C++11:
- 最後のキーワードである final modified class は、クラスを継承できないことを意味します。
class A final
{
// ....
};
1 つのオブジェクトしか作成できない 5 つのクラス - シングルトン モード
設計パターン: イテレーター パターン、アダプター パターン (スタック、キュー)、シングルトン パターン、ファクトリー パターン、オブザーバー パターンなど。
クラスは 1 つのオブジェクトのみを作成できます。つまり、シングルトン モードでは、システム内にクラスのインスタンスが 1 つだけ存在することが保証され、それにアクセスするためのグローバル アクセス ポイントが提供されます。これは、すべてのプログラム モジュールによって共有されます。たとえば、サーバー プログラムでは、サーバーの構成情報がファイルに格納され、これらの構成データがシングルトン オブジェクトによって一律に読み取られ、サービス プロセス内の他のオブジェクトがこのシングルトン オブジェクトを介して構成情報を取得します。このアプローチにより、複雑な環境での構成管理が簡素化されます。
アイデア: クラスは 1 つのオブジェクトしか作成できません。つまり、コンストラクター、コピー コンストラクター、および代入コンストラクターをプライベートにすることです。
飢えた男モード
プログラムの起動時に、一意のインスタンス オブジェクトが作成されます。このシングルトン オブジェクトがマルチスレッドの高同時実行環境で頻繁に使用され、高いパフォーマンスが要求される場合は、ハングリー マン モードを使用してリソースの競合を回避し、応答速度を向上させる方が明らかに優れています。
長所: シンプルで、スレッド セーフの問題はありません。
欠点: クラス内の静的データ オブジェクトの初期化は、メイン関数の前に行われます。オブジェクトの初期化時にデータが多すぎると、プロセスの開始が遅くなる可能性があります。複数のシングルトン クラスに初期化の依存関係があり、複数のシングルトン クラス オブジェクト インスタンスの起動シーケンスが不明であるため、スタービング モードを制御できません。(例えば、AもBもsingletonクラスです。BはAに依存しているので、まずAを初期化し、次にBを初期化する必要があります。ハングリーマンモードでは目的を達成できません)
class Singleton
{
public:
static Singleton* getSingleton()
{
return &_instance;
}
private:
// 构造函数私有
Singleton() {};
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
static Singleton _instance;
};
Singleton Singleton::_instance;//static变量要在类外初始化
int main()
{
Singleton* s = Singleton::getSingleton();
return 0;
}
レイジーモード
プログラムの実行中にオブジェクトが使用されない場合でも、プラグインのロード、ネットワーク接続の初期化、ファイルの読み取りなど、シングルトン オブジェクトの構築に非常に時間がかかるか、多くのリソースを消費する場合、オブジェクトはプログラムの最初にハングリーモードで作成する必要があります初期化するだけです。つまり、読み込みが遅くなり、リソースが浪費されます。
したがって、この場合は遅延モード (遅延読み込み) を使用することをお勧めします。
利点: オブジェクトは、インスタンス オブジェクトが初めて使用されるときに作成されます。プロセスは無負荷で開始されます。複数のシングルトンインスタンスの起動順序を自由に制御できます
。
短所: 複雑なスレッド セーフの問題はロックする必要があり、一般的なシングルトン オブジェクトはリソースの解放を考慮する必要がなく (必要に応じてリソース リサイクルの機能を追加する必要があります)、新しい例外によるロック解除の問題も考慮する必要があります (またはtry catch を使用するか、luckguard を使用します)
template<class Lock>
class LockGuard{
public:
LockGuard(Lock& lk) : _lk(lk)
{ _lk.lock(); }
~LockGuard(){ _lk.unlock(); }
private:
Lock& _lk;//因为锁是不允许拷贝构造的,所以这里要用引用
};
class Singleton
{
public:
static Singleton* getSingleton()
{
//因为单例模式只在第一次调用的时候创建对象,加锁解锁的动作只需要在第一次有,用双检查加锁来实现
if(_pinstance == nullptr){//第1次判断是避免每次都加锁,提高性能
_mtx.lock();
//第2次判断是保证线程安全且只new一次 第一次获取单例对象时创建对象和加锁
try
{
if(_pinstance == nullptr) _pinstance = new Singleton;
}
catch(...)
{
_mtx.unlock();
throw;
}
_mtx.unlock();
}
return _pinstance;
}
// 实现一个内嵌垃圾回收类
class CGarbo {
public:
~CGarbo(){
if (Singleton::_pinstance) delete Singleton::_pinstance;
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
// 构造函数私有
Singleton() {};
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
static Singleton* _pinstance;//唯二的变化 单例对象指针
static mutex _mtx;//唯二的变化 互斥锁
};
Singleton* Singleton::_pinstance = nullptr;//static变量要在类外初始化
Singleton::CGarbo Singleton::CGarbo CGarbo;
mutex Singleton::_mtx;
int main()
{
Singleton* s = Singleton::getSingleton();
return 0;
}
グローバル静的変数とローカル静的変数の初期化
グローバル静的変数のコンストラクタは main の前に実行され、デストラクタは main の後に実行されます。
メインの後にローカル静的変数が初期化される
//该写法是懒汉模式,静态的局部变量在main函数之后被调用才创建初始化的
//c11前对静态局部变量的初始化没有做出相关规定,故无法保证线程安全,c11后作出规定了编译器实现了保证静态局部变量的线程安全
static Singleton& getSingleton()
{
static Singleton inst;//函数体内定义该对象,只会在调用的时候初始化,并且在main函数之后创建
return inst;
}