C++ - 特別なクラスの設計

C++ - 特別なクラスの設計

ブルームフィルター(2)

特別授業

コピーできないクラスを設計する

Copy は 2 つのシナリオでのみ配置されます。1 つはコピー コンストラクターで、もう 1 つは代入演算子のオーバーロードです。したがって、クラスをコピーしたくない場合は、クラスがコピー コンストラクターを呼び出せないようにするだけで済みます。代入演算子のオーバーロード、つまりコピーを無効にします。コンストラクターと代入演算子のオーバーロード

  • C++98 アプローチ
class CopyBan
{
    
    
    // ...
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};
  • コピー構築とコピー代入をプライベート メンバーとして設定し、クラス外から呼び出されないようにします。
  • メンバー関数内でのコピーを防ぐために、宣言のみを行い、定義は行いません。
  • C++11 アプローチ
class CopyBan
{
    
    
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};
  • C++11 では、delete の使用法が拡張されており、new によって適用されたリソースを解放することに加えて、delete は、デフォルトのメンバー関数の後に =delete が続く場合、コンパイラがデフォルトのメンバー関数を削除することを意味します。

ヒープ上にのみ作成できるクラスを設計する

コンストラクターをプライベートにし、コピー コンストラクターを無効にする

//只能在堆上创建对象
class HeapOnly
{
    
    
public:
	static HeapOnly* Createobj()
//用static修饰的函数不再单单属于对象,而是属于类,可以通过类内函数调用。
//但如果不用static修饰,那么调用类的函数意味这先有对象,而外部调用不了构造函数就不能创建对象
	{
    
    
		return new HeapOnly();
	}
private:
	HeapOnly() {
    
    };//禁用构造函数,防止外部调用构造函数在栈上创建对象
	HeapOnly(const HeapOnly& hp) = delete;//防拷贝
};

int main()
{
    
    
	//HeapOnly* hps = new HeapOnly();//这样走需要调用构造函数,然而构造函数不能被调用
	HeapOnly* hps = HeapOnly::Createobj();
	
}
  • コンストラクターをプライベート化すると、コンストラクターへの外部呼び出しによってスタック上および静的領域にオブジェクトが作成されなくなります。
  • コピー構築によってオブジェクトがスタック上または静的領域に作成されるのを防ぐために、コピー構築は削除によって変更されます。
  • Createobj 関数は、HeapOnly ポインターを使用して受け取られる新しい HeapOnly 匿名オブジェクトを返します。
  • Createobj 関数は、関数がオブジェクトではなくクラスに属し、クラス内の関数を通じて呼び出せることを示すために static で装飾されています。そうしないと、オブジェクトの関数を呼び出す前にオブジェクトが存在する必要があり、Createobj 関数を呼び出す機能はオブジェクトを取得することになるため、問題が発生します。

スタック上でのみ作成できるクラスを設計する

方法 1

コンストラクターのプライベート化、コピー構築を通じてスタック上にオブジェクトを作成

//只在栈上创建的对象
class StackOnly
{
    
    
public:
	static StackOnly CreateObj()
	{
    
    
		return StackOnly();//
	}
private:
	StackOnly() {
    
    };//构造函数私有。外部通过new对象也需要走构造函数

};

int main()
{
    
    
	//StackOnly* st = new StackOnly();//构造函数私有化,不能通过new创建对象即不能在堆上创建对象
	//static StackOnly sp1;//要走构造函数
	static StackOnly st1 = StackOnly::CreateObj();//没有封掉拷贝构造函数,这里可以通过拷贝构造函数在静态区上创建
	StackOnly st2= StackOnly::CreateObj();//可以通过拷贝构造在栈上创建对象,但无法防止在静态区上创建
	return 0;
}
  • コンストラクターをプライベート化すると、コンストラクターを呼び出してヒープまたは静的領域にオブジェクトを作成できなくなります。
  • クラス内関数を呼び出してオブジェクトを取得し、コピー構築によってスタック上にオブジェクトを作成しますが、コピー構築によって静的領域にオブジェクトが作成されることは妨げられません

方法 2

コンストラクターをプライベートにし、コピー コンストラクターを無効にする

class StackOnly
{
    
    
public:
	static StackOnly CreateObj()
	{
    
    
		return StackOnly();//
	}

	void Print()const
	{
    
    
		cout << "StackOnly::Print()" << endl;
	}
	~StackOnly() {
    
     cout << "~StackOnly()" << endl; }
	void* operator new(size_t s) = delete;
	void operator delete(void* p) = delete;
	//把new和delete禁掉,不能通过new在堆上创建对象
private:
	StackOnly() {
    
    };//构造函数私有。外部通过new对象也需要走构造函数
	StackOnly(const StackOnly& st) = delete;//防拷贝---防止通过调用拷贝构造在静态区上创建对象


};
int main()
{
    
    
	//static StackOnly sp1;//要走构造函数
	StackOnly::CreateObj().Print();//通过匿名对象在栈上创建对象,走完这一行就调用析构函数释放匿名对象
	const StackOnly& so4 = StackOnly::CreateObj();
	//通过const 引用接收匿名对象,匿名对象的生命周期转化为so4对象的生命周期,即cosnt引用延迟了匿名对象的生命周期。匿名对象在栈上创建
	so4.Print();
	return 0;
}
  • コンストラクターをプライベート化すると、コンストラクターを呼び出してヒープまたは静的領域にオブジェクトを作成できなくなります。
  • コピー構築によってオブジェクトがスタック上または静的領域に作成されるのを防ぐために、コピー構築は削除によって変更されます。
  • new と delete を無効にすると、new を通じてヒープ上にオブジェクトを作成できなくなります。
  • CreateObj は匿名オブジェクトを取得し、匿名オブジェクトを通じてメンバー関数 Print を呼び出し、スタック上にオブジェクトを作成します。しかし、問題は、匿名オブジェクトの宣言サイクルが 1 行だけであることです。つまり、Print 関数を呼び出した直後にオブジェクトが破棄されてしまいます。
  • const 参照を通じて匿名オブジェクトを受け取ると、匿名オブジェクトのライフ サイクルが so4 オブジェクトのライフ サイクルに変換されます。つまり、cosnt 参照により匿名オブジェクトのライフ サイクルが遅れます。匿名オブジェクトがスタック上に作成されます。

継承できないクラスを設計する

C++98方式

class Father
{
    
    
public:

	
private:
	Father() {
    
     cout << "get Father" << endl; };
};

class Son :public Father
{
    
    
public:
	Son() {
    
     cout << "get son" << endl; };
	const Father& getf()
	{
    
    
		return Father();
	}
};

  • 基本クラスのコンストラクターがプライベート化されると、派生クラスは基本クラスのコンストラクターを呼び出すことができず、継承することもできません。

C++11メソッド

//基类用final修饰则表示为最后一一个类,不能被继承
class Father final
{
    
    
public:

	Father() {
    
     cout << "get Father" << endl; };//构造函数私有化则该基类无法被继承
private:
	
};

class Son :public Father
{
    
    
public:
	Son() {
    
     cout << "get son" << endl; };
	const Father& getf()
	{
    
    
		return Father();
	}
};

  • 基本クラスを Final で変更すると、そのクラスは最後のクラスであり、継承できないことを意味します。

シングルトンパターン

デザインパターン

デザイン パターンは、繰り返し使用され、ほとんどの人に知られ、要約された一連の分類されたコード設計エクスペリエンスです。

デザイン パターンを使用する目的:コードの再利用性、他の人がコードを理解しやすくすること、およびコードの信頼性を確保することデザイン パターンにより、コード記述は真のエンジニアリングになります。デザイン パターンは、建物の構造と同じように、ソフトウェア エンジニアリングの基礎となります。

シングルトンパターン

クラスは 1 つのオブジェクトのみを作成でき、クラスはこの 1 つのオブジェクトのみをグローバルに持つことができます(つまり、シングルトン モード)。このモードでは、システム内にクラスのインスタンスが 1 つだけ存在することが保証され、それにアクセスするためのグローバル アクセス ポイントが提供されます。このインスタンスは、すべてのプログラム モジュールによって所有され、共有されます。

たとえば、サーバー プログラムでは、サーバーの構成情報がファイルに格納されており、これらの構成データはシングルトン オブジェクトによって均一に読み取られ、サービス プロセス内の他のオブジェクトはこのシングルトン オブジェクトを通じて構成情報を取得します。複雑な環境での構成管理を簡素化します。シングルトン モードには 2 つの一般的に使用される実装モードがあり、1 つはハングリー モード、もう 1 つはレイジー モードです。

ハングリーモード

  • ハングリーモードは比較的シンプルです。
  • 後続のプログラムがそれを使用するかどうかに関係なく、オブジェクトは main 関数の前に作成されます。
  • 欠点は、まず、ハングリー モードのオブジェクトが多い場合、プロジェクトの起動速度が遅くなる可能性があること、第 2 に、作成されるオブジェクトの順序が OS によって決定され、作成されたオブジェクトの間に順序がある場合、問題が発生する可能性があることです。エラー。たとえば、A オブジェクトを作成する前に、B オブジェクトはインターフェイスを提供する必要があります。OS が最初に A オブジェクトを作成し、次にハングリー モードで B オブジェクトを作成すると、A オブジェクトの作成時に B オブジェクトは存在せず、インターフェイスが提供されます。を呼び出すことができないため、A オブジェクトの作成は失敗します。

#pragma once
#include<iostream>
#include<map>
#include<string.h>
#include<mutex>
using namespace std;
class SingalInfo
{
    
    
public:
	static SingalInfo&getbody()
	{
    
    
		return _sig;
	}
	void Insert(const string& name,int salary)
	{
    
    
		_info[name] = salary;
	}
	void Print()
	{
    
    
		for (auto& kv : _info)
		{
    
    
			cout << kv.first << " : " << kv.second << endl;
		}
		cout << endl;
	}
private:
	SingalInfo() {
    
    };//构造函数私有化
	SingalInfo(SingalInfo& sig) = delete;//禁用拷贝构造
	SingalInfo& operator=(SingalInfo& sig) = delete;//禁用拷贝赋值
private:
 static	SingalInfo _sig;//类内声明---整个类只有一个对象
 map<string, int> _info;

};
SingalInfo SingalInfo::_sig;//类外定义对象,全局定义具有全局属性,声明周期是全局


int main()
{
    
    
	SingalInfo& kk = SingalInfo::getbody();
	kk.Insert("me", 10000);
	kk.Insert("you", 20000);
	kk.Insert("who", 30000);

	kk.Print();

	SingalInfo::getbody().Insert("she", 25000);
	SingalInfo::getbody().Print();

	kk.Print();//此时kk对象打印的内容和前面的匿名对象打印的内容是一样的表示全局内对象只有一份
	return 0;
}
  • コンストラクターをプライベート化すると、オブジェクトを作成するためのコンストラクターへの外部呼び出しが防止されます。
  • コピー構築とコピー割り当ては削除によって変更され、コピーによるオブジェクトの作成は禁止されます。
  • クラス内のメンバー _sig の型は SingalInfo です。これは、メンバーの型が現在のクラスであることを意味します。
  • クラス内メンバー _sig は static で変更されます。これは、メンバーがオブジェクトだけでなくクラス全体にも属することを意味し、クラス内にオブジェクトが 1 つだけであることを示します。
  • クラス内メンバー _sig はクラス内で宣言され、クラス外でグローバルに定義され、メンバーがグローバル属性を持つことを示します。
  • getbody 関数を通じて _sig メンバーを取得し、SingalInfo 参照を使用してそれを受け取ります。外部的には、クラス オブジェクトは getbody 関数を呼び出して取得され、参照によって受信されます。
  • クラス内にはデータを保存するためのマップがあります。

画像-20230909154437081

注: このシングルトン オブジェクトがマルチスレッドの高同時実行環境で頻繁に使用され、高いパフォーマンス要件がある場合は、明らかにハングリー モードを使用してリソースの競合を回避し、応答速度を向上させる方が良いでしょう。

レイジーモード

シングルトン オブジェクトの構築に非常に時間がかかる場合や、プラグインの読み込み、ネットワーク接続の初期化、ファイルの読み取りなど、多くのリソースを消費する場合、そのオブジェクトが使用されない可能性があります。プログラムが実行中の場合は、プログラムの先頭で行う必要があります。初期化するだけでは、プログラムの起動が非常に遅くなります。したがって、この場合は遅延モード (遅延読み込み) を使用することをお勧めします。

  • 遅延モードでは、main 関数が開始された後にオブジェクトが作成されます。
  • 起動シーケンスは弊社にて決定させていただきます。
  • 欠点は、実装がより複雑になることです。
#pragma once
#include<iostream>
#include<map>
#include<string.h>
#include<mutex>
using namespace std;

template<class lock>
class LockGuard
{
    
    
public:
	LockGuard(lock& lk) :_lk(lk)
	{
    
    
		_lk.lock();
	}

	~LockGuard()
	{
    
    
		_lk.unlock();
	}
		lock& _lk;
};

class SingalInfo
{
    
    
public:
	static SingalInfo& getbody()
	{
    
    //当多个线程进来时,会new多个对象,因此需要加锁保护
		if (_sig == nullptr)//第一次进来时指针为空,就创建新对象,否则直接返回此对象
		{
    
    
			LockGuard<mutex> lg(_mut);
		//	_mut.lock();//双重检查:第一次进来且只有一个线程能够new对象
			if (_sig == nullptr)
			{
    
    
				_sig = new SingalInfo();
			}
		//	_mut.unlock();//这样出现new对象失败抛异常导致解锁失败
		}
		return *_sig;
	}
//
	static void DeleteInstance()
	{
    
    
		//...保存数据
		LockGuard<mutex> lg(_mut);
		if (_sig)
		{
    
    
			cout << "~ _sig" << endl;
			delete _sig;
			_sig = nullptr;
		}
	}
//
	class GC//内部类相当于友元,直接拿外类的成员
	{
    
    
	public:
		~GC()
//定义一个内部类,可以在main函数随处位置调用释放外部类,或者当外部类的声明周期结束时自动调用该内部类的析构函数释放外部类。而不是让创建当前外部类的父进程回收外部类
		{
    
    
			
			DeleteInstance();
		}
	};

	void Insert(const string& name, int salary)
	{
    
    
		_info[name] = salary;
	}
	void Print()
	{
    
    
		for (auto& kv : _info)
		{
    
    
			cout << kv.first << " : " << kv.second << endl;
		}
		cout << endl;
	}
private:
	SingalInfo() {
    
     cout << "touch SingalInfo" << endl; };//构造函数私有化
	SingalInfo(SingalInfo& sig) = delete;//禁用拷贝构造
	SingalInfo& operator=(SingalInfo& sig) = delete;//禁用拷贝赋值
private:
	static	SingalInfo* _sig;//类内声明---整个类只有一个对象
	map<string, int> _info;
	static mutex _mut;
	static GC _gc;//内部类声明

};
SingalInfo* SingalInfo::_sig=nullptr;//类外定义对象,全局定义具有全局属性,声明周期是全局
mutex SingalInfo::_mut;
SingalInfo::GC SingalInfo::_gc;
int main()
{
    
    
	SingalInfo& kk = SingalInfo::getbody();
	kk.Insert("me", 10000);
	kk.Insert("you", 20000);
	kk.Insert("who", 30000);

	kk.Print();

	SingalInfo::getbody().Insert("she", 25000);
	SingalInfo::getbody().Print();

	kk.Print();//此时kk对象打印的内容和前面的匿名对象打印的内容是一样的表示全局内对象只有一份
	return 0;
}
  • クラスで宣言されたメンバー _sig は SingalInfo ポインター型であり、static で変更され、メンバーがクラス全体に属していることを示します。クラスの外部でグローバル プロパティとして定義されている場合、それはグローバルであり、宣言サイクルもグローバルであることを意味します。

  • _mut は、重要なセクションを保護するために使用されるミューテックス ロックです。ロックとロック解除の間のコードはクリティカル セクションと呼ばれます。ロック後は、同時に 1 つのスレッドのみがクリティカル セクションに入ることができ、スレッドの安全性が維持されます。

  • マップは、データを格納するために使用されるキーと値のペア タイプのデータ構造です。

  • _gc は内部クラスです。内部クラスはフレンド関数に相当します。オブジェクト _sig の解放とロック _mut の解放を担当する外部クラスのメンバーである DeleteInstance 関数を呼び出すことができます。DeleteInstance 関数を呼び出さずにリソースを解放できます。プロセスのロックが解除されると、親プロセスがリソースをリサイクルして解放します。プログラムのロックが解除されているときに、内部クラスの GC サイクルの最後にデストラクターを自動的に呼び出してから、DeleteInstance 関数を呼び出してリソースを解放することもできます。

  • LockGuard クラスはロックとロック解除を担当します。このクラスは RAII タイプです。外部ミューテックス ロックが渡されます。コンストラクターはロックをロックし、デストラクターはロックのロックを解除します。

  • オブジェクトの取得にはgetbody関数を使用します。静的変更を使用して、クラス内の関数を通じて呼び出せることを示します。

  • メンバー _sig==nullptr を二重チェックする getbody 関数の機能は、getbody 関数の呼び出しが初めてであるかどうかをチェックすることです。最初の呼び出しは、オブジェクトがまだ作成されていないことを示します。2 番目のチェックは、1 つのスレッドをロックしてオブジェクトへのアクセスを制限するために使用されます。

  • ミューテックス ロック _mut を LockGuard でパッケージ化する目的は、外側の if 関数を終了するときに LockGuard オブジェクトのデストラクターを自動的に呼び出してロックを解除し、新しい関数が例外をスローして現在の関数を終了できないことによるロック解除の失敗の問題を回避することです。スタックフレーム。

画像-20230909164043447

実際、オブジェクトを取得するとき、新しいオブジェクトを通じてオブジェクトが作成され、コンストラクターが受信され、ポインターが受信されます。スレッドの安全性の問題がある可能性があるため、ロック保護、二重チェック、その後のロック解除とロック リソースの解放が必要になります。 、などが必要となります。C++11 では、静的オブジェクトの作成がスレッド セーフであることが保証されているため、静的オブジェクトを取得してスレッド セーフの問題を回避し、ミューテックス ロックなどのリソースの呼び出しと解放を放棄できます。

class SingalInfo
{
    
    
public:
	static SingalInfo& GetInstance()
	{
    
    
		static SingalInfo instance;//静态的局部变量是在main函数之后创建的
		//C++11之后能够保证创建静态对象是线程安全的
		return instance;
	}
   
	void Insert(const string& name, int salary)
	{
    
    
		_info[name] = salary;
	}
	void Print()
	{
    
    
		for (auto& kv : _info)
		{
    
    
			cout << kv.first << " : " << kv.second << endl;
		}
		cout << endl;
	}
private:
	SingalInfo() {
    
     cout << "touch SingalInfo" << endl; };//构造函数私有化
	SingalInfo(SingalInfo& sig) = delete;//禁用拷贝构造
	SingalInfo& operator=(SingalInfo& sig) = delete;//禁用拷贝赋值
private:
	map<string, int> _info;
};

おすすめ

転載: blog.csdn.net/m0_71841506/article/details/132779897