C++ - conception de classe spéciale

C++ - conception de classe spéciale

Filtre de floraison(2)

Classe spéciale

Concevoir une classe qui ne peut pas être copiée

La copie ne sera placée que dans deux scénarios, l'un est le constructeur de copie et l'autre est la surcharge de l'opérateur d'affectation. Par conséquent, si vous souhaitez qu'une classe ne soit pas copiée, il vous suffit de rendre la classe incapable d'appeler le constructeur de copie et Surcharge des opérateurs d'affectation, c'est-à-dire désactiver la copie. Surcharge des constructeurs et des opérateurs d'affectation

  • Approche C++98
class CopyBan
{
    
    
    // ...
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};
  • Définissez la construction et l'affectation des copies en tant que membres privés pour éviter qu'ils ne soient appelés en dehors de la classe.
  • Déclarez uniquement mais pas définissez pour empêcher la copie à l'intérieur de la fonction membre.
  • Approche C++11
class CopyBan
{
    
    
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};
  • C++11 étend l'utilisation de delete. En plus de libérer les ressources appliquées par new, delete signifie que si la fonction membre par défaut est suivie de =delete, cela signifie que le compilateur supprimera la fonction membre par défaut.

Concevoir une classe qui ne peut être créée que sur le tas

Rendre le constructeur privé et désactiver le constructeur de copie

//只能在堆上创建对象
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();
	
}
  • La privatisation du constructeur empêche les appels externes au constructeur de créer des objets sur la pile et dans la zone statique.
  • La construction de copie est modifiée avec delete pour empêcher la création d'objets sur la pile ou dans la zone statique via la construction de copie.
  • La fonction Createobj renvoie un nouvel objet anonyme HeapOnly, qui est reçu à l'aide du pointeur HeapOnly ;
  • La fonction Createobj est décorée de static pour indiquer que la fonction n'appartient pas à un objet, mais à une classe, et peut être appelée via des fonctions au sein de la classe. Sinon, il doit y avoir un objet avant d'appeler la fonction de l'objet, et la fonction de l'appel de la fonction Createobj est d'obtenir un objet, donc des problèmes surgiront.

Concevoir une classe qui ne peut être créée que sur la pile

première méthode

Privatisation du constructeur, création d'objets sur la pile via la construction par copie

//只在栈上创建的对象
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;
}
  • La privatisation du constructeur empêche l'appel du constructeur de créer des objets sur le tas ou dans la zone statique.
  • Appelez la fonction en classe pour obtenir l'objet, puis créez l'objet sur la pile via la construction par copie, mais cela n'empêche pas la création de l'objet dans la zone statique via la construction par copie .

Deuxième méthode

Rendre le constructeur privé et désactiver le constructeur de copie

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;
}
  • La privatisation du constructeur empêche l'appel du constructeur de créer des objets sur le tas ou dans la zone statique.
  • La construction de copie est modifiée avec delete pour empêcher la création d'objets sur la pile ou dans la zone statique via la construction de copie.
  • Désactivez new et delete, et les objets ne peuvent pas être créés sur le tas via new.
  • CreateObj obtient un objet anonyme et appelle la fonction membre Print via l'objet anonyme pour créer l'objet sur la pile. Mais le problème est que le cycle de déclaration de l'objet anonyme ne comporte qu'une seule ligne, c'est-à-dire que l'objet est détruit immédiatement après l'appel de la fonction Print.
  • Lors de la réception d'un objet anonyme via une référence const, le cycle de vie de l'objet anonyme est converti en cycle de vie de l'objet so4, c'est-à-dire que la référence cosnt retarde le cycle de vie de l'objet anonyme. Des objets anonymes sont créés sur la pile.

Concevoir une classe qui ne peut pas être héritée

Façon 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();
	}
};

  • Si le constructeur de la classe de base est privatisé, la classe dérivée ne peut pas appeler le constructeur de la classe de base et ne peut pas hériter.

Méthode 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();
	}
};

  • Modifier la classe de base avec final signifie que la classe est la dernière classe et ne peut pas être héritée.

Modèle singleton

Modèles de conception

Le modèle de conception est un ensemble d'expériences de conception de code classifiées qui sont utilisées à plusieurs reprises, connues de la plupart des gens et résumées .

Le but de l'utilisation de modèles de conception : pour la réutilisabilité du code, pour rendre le code plus facile à comprendre par les autres et pour garantir la fiabilité du code . Les modèles de conception font de l'écriture de code une véritable ingénierie ; les modèles de conception sont la pierre angulaire du génie logiciel, tout comme la structure d'un bâtiment.

Modèle singleton

Une classe ne peut créer qu'un seul objet et la classe ne peut avoir que cet objet globalement ,C'est-à-dire le mode singleton.Ce mode peut garantir qu'il n'y a qu'une seule instance de la classe dans le système et fournir un point d'accès global pour y accéder. Cette instance appartient à tous les modules du programme et est partagée.

Par exemple, dans un programme serveur, les informations de configuration du serveur sont stockées dans un fichier. Ces données de configuration sont lues uniformément par un objet singleton, puis d'autres objets du processus de service obtiennent les informations de configuration via cet objet singleton. Cette méthode simplifie la gestion de la configuration dans des environnements complexes. Il existe deux modes d'implémentation couramment utilisés du mode singleton, l'un est le mode affamé et l'autre est le mode paresseux.

Mode faim

  • Le mode faim est relativement simple.
  • Que le programme suivant l'utilise ou non, l'objet est créé avant la fonction principale.
  • Les inconvénients sont : premièrement, s'il y a beaucoup d'objets en mode Hungry, la vitesse de démarrage du projet peut être lente ; deuxièmement, l'ordre des objets créés est déterminé par le système d'exploitation, et s'il existe une séquence entre les objets créés, cela peut provoquer les erreurs. Par exemple, avant de créer un objet A, l'objet B doit fournir une interface. Si le système d'exploitation crée d'abord l'objet A, puis l'objet B en mode Hungry Mode, l'objet B n'existera pas lorsque l'objet A sera créé et l'interface ne peut pas être appelé, donc la création de l’objet A échoue.

#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;
}
  • La privatisation du constructeur empêche les appels externes au constructeur pour créer des objets.
  • La construction et l'affectation de copie sont modifiées par la suppression, et la création d'objets par copie est interdite.
  • Le type du membre _sig dans la classe est SingalInfo, ce qui signifie que le type du membre est la classe actuelle.
  • Le membre intra-classe _sig est modifié avec static, ce qui signifie que le membre n'appartient pas seulement à l'objet, mais à la classe entière, indiquant qu'il n'y a qu'un seul objet dans une classe.
  • Le membre intra-classe _sig est déclaré dans la classe et défini globalement en dehors de la classe, indiquant que le membre possède des attributs globaux.
  • Obtenez le membre _sig via la fonction getbody et utilisez la référence SingalInfo pour le recevoir. En externe, l'objet de classe est obtenu en appelant la fonction getbody et reçu par référence.
  • Il existe une carte dans la classe pour stocker les données.

image-20230909154437081

Remarque : Si cet objet singleton est fréquemment utilisé dans un environnement multithread à haute concurrence et a des exigences de performances élevées, il est évidemment préférable d'utiliser le mode affamé pour éviter la concurrence entre les ressources et améliorer la vitesse de réponse.

mode paresseux

Si la construction d'un objet singleton prend beaucoup de temps ou consomme beaucoup de ressources, comme le chargement de plug-ins, l'initialisation des connexions réseau, la lecture de fichiers, etc., et qu'il est possible que l'objet ne soit pas utilisé lorsque le Si le programme est en cours d'exécution, cela doit être fait au début du programme. Le simple fait de l'initialiser entraînera un démarrage très lent du programme. Il est donc préférable d'utiliser le mode paresseux (chargement paresseux) dans ce cas.

  • En mode paresseux, les objets sont créés après le démarrage de la fonction principale.
  • La séquence de démarrage peut être décidée par nos soins.
  • L’inconvénient est que la mise en œuvre est plus compliquée.
#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;
}
  • Le membre _sig déclaré dans la classe est un type de pointeur SingalInfo et modifié avec static, indiquant que le membre appartient à la classe entière. Défini comme une propriété globale en dehors de la classe, cela signifie qu'elle est globale et que le cycle de déclaration est global.

  • _mut est un verrou mutex utilisé pour protéger les sections critiques. Le code entre le verrouillage et le déverrouillage est appelé section critique. Après le verrouillage, un seul thread peut entrer dans la section critique en même temps, garantissant ainsi la sécurité des threads.

  • La carte est une structure de données de type paire clé-valeur utilisée pour stocker des données.

  • _gc est une classe interne. La classe interne est équivalente à une fonction ami. Vous pouvez appeler la fonction DeleteInstance, membre de la classe externe, qui est responsable de la libération de l'objet _sig et de la libération du verrou _mut. Vous pouvez libérer des ressources sans appeler la fonction DeleteInstance. Lorsque le processus est déverrouillé, le processus parent les recyclera et les libérera. Il est également possible d'appeler automatiquement le destructeur à la fin du cycle GC de classe interne lorsque le programme est déverrouillé, puis d'appeler la fonction DeleteInstance pour libérer des ressources.

  • La classe LockGuard est responsable du verrouillage et du déverrouillage. Cette classe est de type RAII. Un verrou mutex externe est passé. Le constructeur verrouille le verrou et le destructeur déverrouille le verrou.

  • La fonction getbody est utilisée pour obtenir l'objet. Utilisez la modification statique pour indiquer qu'elle peut être appelée via des fonctions au sein de la classe.

  • La fonction de la fonction getbody pour vérifier le membre _sig==nullptr est de vérifier si c'est la première fois que la fonction getbody est appelée.Le premier appel indique que l'objet n'a pas encore été créé. La deuxième vérification est utilisée pour verrouiller et empêcher un thread d'accéder à l'objet.

  • Le but de l'empaquetage du mutex lock _mut avec LockGuard est d'appeler automatiquement le destructeur de l'objet LockGuard pour le déverrouiller lors de la sortie de la fonction if externe, évitant ainsi le problème de l'échec du déverrouillage dû à l'échec de new à lever une exception et à quitter le courant. cadre de pile.

image-20230909164043447

En fait, lors de l'acquisition de l'objet, l'objet est créé via le nouvel objet, le constructeur est reçu et le pointeur est reçu. Il peut y avoir des problèmes de sécurité des threads, donc protection du verrou, double vérification, déverrouillage ultérieur et libération de la ressource de verrouillage , etc. sont requis. En C++11, il est garanti que la création d'objets statiques est thread-safe. Par conséquent, vous pouvez obtenir des objets statiques pour éviter les problèmes de sécurité des threads et abandonner l'appel et la libération de ressources telles que les verrous mutex .

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;
};

Je suppose que tu aimes

Origine blog.csdn.net/m0_71841506/article/details/132779897
conseillé
Classement