Partage de technologie

Découvrez la puissance des pointeurs intelligents

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina


Catalogue de cours



1. Connaissance de base des pointeurs intelligents

Inconvénients des pointeurs bruts :

  1. oublierdeleteLibérer des ressources, provoquant une fuite de ressources
  2. existerdeleteLe programme s'est terminé normalement auparavant (par exemple, siifmilieureturn) ou sortir anormalement avant qu’il ne soit trop tarddelete, conduisant à une fuite de ressources
  3. La même ressource est libérée plusieurs fois, provoquant la libération de pointeurs sauvages et le crash du programme.

À l’heure actuelle, des pointeurs intelligents sont nécessaires. Le mot intelligence des pointeurs intelligents se reflète principalement dans le fait que les utilisateurs n'ont pas besoin de prêter attention à la libération des ressources, car les pointeurs intelligents vous aideront à gérer complètement la libération des ressources. Ils garantiront que quelle que soit la façon dont la logique du programme s'exécute. , il s'exécutera normalement ou générera des exceptions.Lorsque les ressources expirent (portée de la fonction ou fin du programme), elles seront libérées.

Implémentons nous-mêmes un simple pointeur intelligent

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr() { delete mptr; }
private:
	T* mptr;
};

int main()
{
	CSmartPtr<int> ptr(new int());
	/*其它的代码...*/

	/*
	由于ptr是栈上的智能指针对象,不管是函数正常执行完,
	还是运行过程中出现异常,栈上的对象都会自动调用析构函数,
	在析构函数中进行了delete操作,保证释放资源
	*/

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. Les pointeurs intelligents sont incarnés dans une encapsulation orientée objet de pointeurs bruts, initialisant les adresses de ressources dans le constructeur et libérant les ressources dans le destructeur.
  2. Grâce à la fonctionnalité de destruction automatique des objets sur la pile, les ressources sont garanties d'être libérées dans le destructeur du pointeur intelligent.

Par conséquent, en raison des caractéristiques ci-dessus, les pointeurs intelligents sont généralement définis sur la pile. en même temps,Un pointeur intelligent est un objet de classe , un pointeur est passé dans le constructeur de cette classe, et le pointeur passé est libéré dans le destructeur.Puisque ce type d'objet est alloué et libéré sur la pile, il sera automatiquement libéré à la fin de notre fonction (ou programme).

Alors, des pointeurs intelligents peuvent-ils être définis sur le tas ?Par exempleCSmartPtr* p = new CSmartPtr(new int);, la compilation peut passer, mais la définition icipBien qu'il s'agisse d'un type de pointeur intelligent, il s'agit essentiellement d'un pointeur brut, doncpEncore faut-il le faire manuellementdelete, cela revient au problème auquel nous avons été confrontés avec les pointeurs simples au début, alors ne l'utilisez pas comme ça

Bien entendu, les pointeurs intelligents doivent être similaires aux pointeurs bruts et doivent également fournir des fonctionnalités communes aux pointeurs bruts.*et->Les fonctions surchargées des deux opérateurs sont véritablement les mêmes que les pointeurs bruts lorsqu'ils sont utilisés. Le code est le suivant :

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr() { delete mptr; }

	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }

private:
	T* mptr;
};


int main()
{
	CSmartPtr<int> ptr(new int());

	*ptr = 20;	// operator*()一定要返回引用,这样才可以赋值

	cout << *ptr << endl;	// 20

	class Test
	{
	public:
		void test() { cout << "call Test::test()" << endl; }
	};

	CSmartPtr<Test> ptr2 = new Test();

	(*ptr2).test();	// (*ptr2)取出Test对象,用对象调用方法

	// operator->()返回的是一个指针,实现了用指针调用函数
	// 即(ptr2.operator->()) -> test()
	ptr2->test();

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2. Pointeurs intelligents sans comptage de références

Le pointeur intelligent implémenté dans la section précédente est très similaire à un pointeur brut ordinaire utilisé, mais il présente encore de gros problèmes. Voir le code suivant :

CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1);	// 拷贝构造
  • 1
  • 2

Le code en cours d'exécution plante directement car le constructeur de copie par défaut effectue une copie superficielle.p1etp2tient la même chosenew intRessource,p2La destruction libère d'abord les ressources, puisp1Une fois détruit, il devientdeletePointeur sauvage, le programme plante

Alors, comment résoudre les problèmes causés par la copie superficielle ?récrireCSmartPtrJetez un oeil au constructeur de copie

CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
  • 1

Maintenant, le code s'exécute normalement, mais à ce stadep1etp2Deux ressources différentes sont gérées. Si les utilisateurs ne les comprennent pas, ils penseront à tort que cela est le cas.p1etp2Gérer la même ressource

Par conséquent, nous avons tort d’écrire le constructeur de copie comme ceci

Alors, comment résoudre le problème de copie superficielle des pointeurs intelligents ? Deux méthodes :Pointeurs intelligents sans comptage de références, pointeurs intelligents avec comptage de références

Regardons d'abord cette sectionPointeurs intelligents sans comptage de références

  1. auto_ptr(C++98, désormais obsolète)
  2. scoped_ptr(Bibliothèque Boost)
  3. unique_ptr(C++11, recommandé)

Incluez le fichier d'en-tête lorsque vous utilisez :#include <memory>

auto_ptr

Considérons d'abord ce code

auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);

*ptr2 = 20;
cout << *ptr1 << endl;
  • 1
  • 2
  • 3
  • 4
  • 5

L’opération a échoué. Pourquoi ?auto_ptrcode source

Insérer la description de l'image ici

Vous pouvez voir que la construction par copie appellera l'objet entrantreleaseméthode, cette méthode consiste à mettre_RightLa ressource pointée est renvoyée vers le nouveauauto_ptrobjet, et en même temps_Right(vieuxauto_ptr)de_Myptrmis ànullptr

Bref, c'est remettre du vieuxauto_ptrLa ressource pointée est donnée au nouveauauto_ptrTenu, l'ancien est réglé surnullptr .Par conséquent, le code ci-dessus utilise*ptr1Tout simplement faux.auto_ptrLaissez toujours le dernier pointeur intelligent gérer la ressource)

Donc,auto_ptrPeut-il être utilisé dans un conteneur ? Voir le code suivant.

int main()
{
	vector<auto_ptr<int>> vec;
	vec.push_back(auto_ptr<int>(new int(10)));
	vec.push_back(auto_ptr<int>(new int(20)));
	vec.push_back(auto_ptr<int>(new int(30)));
	
	cout << *vec[0] << endl;	// 10
	
	vector<auto_ptr<int>> vec2 = vec;
	/* 这里由于上面做了vector容器的拷贝,
	相当于容器中的每一个元素都进行了拷贝构造,
	原来vec中的智能指针全部为nullptr了,
	再次访问就成访问空指针了,程序崩溃
	*/
	
	cout << *vec[0] << endl;	// 程序崩溃
	
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Il est donc obsolète en C++auto_ptr , à moins que le scénario d'application ne soit très simple.etauto_ptrObsolète en C++11 et entièrement supprimé en C++17

Résumer:auto_ptrLes pointeurs intelligents n'ont pas de comptage de références, ils résolvent donc le problème de la copie superficielle en copiant directement le précédent.auto_ptrsont réglés surnullptr, ne laisse que le dernierauto_ptrdétenir des ressources

portée_ptr

Vous devez installer la bibliothèque Boost, qui inclut les fichiers d'en-tête.#include <boost/scoped_ptr.hpp>Prêt à l'emploi

regardescoped_ptrCode source:

template<class T> class scoped_ptr
{
private:
	scoped_ptr(scoped_ptr const&);
	scoped_ptr& operator=(scoped_ptr const&);
...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

peut être vuscoped_ptrLe constructeur de copie et la fonction d'affectation sont tous deux privatisés, de sorte que l'objet ne prend pas en charge ces deux opérations et ne peut pas être appelé, ce qui empêche fondamentalement l'apparition de copies superficielles.

doncscoped_ptrIl ne peut pas être utilisé dans des conteneurs. Si les conteneurs se copient ou s'attribuent des valeurs, cela entraînerascoped_ptrConstructeur de copie d'objet et fonction d'affectation, erreur de compilation

scoped_ptretauto_ptrLa différence: Peut s'expliquer par la propriété,auto_ptrLa propriété des ressources peut être transférée à volonté, tandis quescoped_ptrLa propriété n'est pas transférée (car les constructeurs de copie et les fonctions d'affectation sont désactivés)

scoped_ptrGénéralement non utilisé

unique_ptr

Jetez un oeil d'abordunique_ptrUne partie du code source :

template<class _Ty, class _Dx>
class unique_ptr
{
public:
	/*提供了右值引用的拷贝构造函数*/
	unique_ptr(unique_ptr&& _Right) { ... }
	
	/*提供了右值引用的operator=赋值重载函数*/
	unique_ptr& operator=(unique_ptr&& _Right) { ... }

	/*
	删除了unique_ptr的拷贝构造和赋值函数,
	因此不能做unique_ptr智能指针对象的拷贝构造和赋值,
	防止浅拷贝的发生
	*/
	unique_ptr(const unique_ptr&) = delete;
	unique_ptr& operator=(const unique_ptr&) = delete;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Vue d'en haut,unique_ptrun petit peuscoped_ptrLa même chose est faite, c'est-à-dire que les fonctions de construction de copie et de surcharge d'affectation sont désactivées et qu'il est interdit aux utilisateurs d'utiliserunique_ptrEffectuez une construction et une affectation de copie explicites pour éviter l’apparition de problèmes de copie superficielle du pointeur intelligent.

Puisqu'il n'y a pas de constructeur de copie,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);Naturellement, c'est faux

maisunique_ptrFournit des constructeurs de copie et des fonctions d'affectation avec des paramètres de référence rvalue, c'est-à-direunique_ptrLes pointeurs intelligents peuvent effectuer des opérations de construction de copie et d'affectation via des références rvalue, ou générerunique_ptrUn emplacement pour des objets temporaires, par ex.unique_ptrEn tant que valeur de retour de la fonction, l'exemple de code est le suivant :

// 示例1
unique_ptr<int> p1(new int());
unique_ptr<int> p2(move(p1));	// 使用了右值引用的拷贝构造
p2 = move(p1);	// 使用了右值引用的operator=赋值重载函数
  • 1
  • 2
  • 3
  • 4
// 示例2
unique_ptr<int> test_uniqueptr()
{
	unique_ptr<int> ptr(new int());
	return ptr;
}
int main()
{
	/*
	此处调用test_uniqueptr函数,在return ptr代码处,
	调用右值引用的拷贝构造和赋值函数
	*/
	unique_ptr<int> p = test_uniqueptr();	// 调用带右值引用的拷贝构造函数
	p = test_uniqueptr();	// 调用带右值引用的operator=赋值重载函数
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Utilisez ensuiteunique_ptrL'avantage est que les utilisateurs peuvent utiliser quelque chose commeunique_ptr<int> p2(move(p1));déclaration, on voit clairement quep1Ressources transférées àp2p1Aucune ressource n'est retenue si vous l'utilisez.auto_ptrCe ne sera pas explicitementmoveÉcrite, l’intention n’est pas évidente. Si vous ne comprenez pas la couche sous-jacente, vous l’utiliserez de manière incorrecte.

En même temps, deunique_ptrComme son nom l'indique, il ne peut y avoir qu'un seul pointeur intelligent faisant référence à une ressource. Par conséquent, il est recommandé de donner la priorité à l'utilisation de pointeurs intelligents sans comptage de références.unique_ptr

3. Pointeurs intelligents avec comptage de références

Les pointeurs intelligents avec comptage de références incluent principalementshared_ptretweak_ptr, avec comptage de référencesavantageAutrement dit, plusieurs pointeurs intelligents peuvent gérer la même ressource. Alors, que sont les pointeurs intelligents avec comptage de références ?

Avec comptage de références : fait correspondre un nombre de références pour la ressource de chaque objet.

Lorsque plusieurs pointeurs intelligents sont autorisés à pointer vers la même ressource, chaque pointeur intelligent ajoutera 1 au nombre de références de la ressource. Lorsqu'un pointeur intelligent est détruit, il diminuera également le nombre de références de la ressource de 1, de sorte que le pointeur intelligent soit autorisé à pointer vers la même ressource. le dernier pointeur intelligent lorsque le nombre de références d'une ressource passe de 1 à 0, cela signifie que la ressource peut être libérée. Le destructeur du dernier pointeur intelligent gère la libération de la ressource.

  • Lors du comptage de référencesmoins 1 n'est pas 0Lorsque , le pointeur intelligent actuel n'utilise plus cette ressource, mais il existe d'autres pointeurs intelligents utilisant cette ressource. Le pointeur intelligent actuel ne peut pas détruire cette ressource et ne peut y accéder que directement.
  • Lors du comptage de référencesDiminuer 1 à 0Quand, cela signifie que le pointeur intelligent actuel est le dernier pointeur intelligent à utiliser cette ressource, il est donc responsable de la libération de cette ressource.

Simuler l'implémentation d'un pointeur intelligent avec comptage de références

Prenez ce qui a été mis en œuvre précédemmentCSmartPtrApportez des modifications et téléchargez le code directement :

// 对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
	RefCnt(T* ptr = nullptr) : mptr(ptr)
	{
		if (mptr != nullptr)
			mcount = 1;
	}
	void addRef() { mcount++; }	// 增加资源的引用计数
	int delRef() { return --mcount; }

private:
	T* mptr;
	int mcount;
};

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr)
	{
		// 智能指针构造的时候给资源建立引用计数对象
		mpRefCnt = new RefCnt<T>(mptr);
	}
	~CSmartPtr()
	{
		if (0 == mpRefCnt->delRef())
		{
			delete mptr;
			mptr = nullptr;
		}
	}

	// 实现拷贝构造
	CSmartPtr(const CSmartPtr<T>& src)
		:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
	{
		if (mptr != nullptr)
			mpRefCnt->addRef();
	}
	CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
	{
		// 防止自赋值
		if (this == &src)
			return *this;

		// 本身指向的资源减1
		// 如果减1为0释放资源;如果减1不为0直接走
		if (0 == mpRefCnt->delRef()) { delete mptr; }

		mptr = src.mptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}

	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }

private:
	T* mptr;	// 指向资源的指针
	RefCnt<T>* mpRefCnt;	// 指向该资源引用计数对象的指针
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;

*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl;	// 20 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Cela permet à plusieurs pointeurs intelligents de gérer la même ressource.

Cependant, les pointeurs intelligents que nous implémentons actuellement ne sont pas thread-safe et ne peuvent pas être utilisés dans des scénarios multithread.Mis en œuvre par Curryshared_ptretweak_ptrC'est thread-safe !

problème de référence croisée shared_ptr

  • shared_ptrpuissantPointeurs intelligents (peuvent modifier le nombre de références des ressources)
  • weak_ptrfaiblePointeurs intelligents (ne modifiez pas le nombre de références de la ressource)

Ce que nous avons implémenté dans la section précédenteCSmartPtrÉgalement un pointeur intelligent puissant (peut modifier le nombre de références de la ressource)

Cela peut être compris de cette façon: Faible pointeur intelligent observer Des pointeurs intelligents puissants Des pointeurs intelligents puissantsobserver Ressources (mémoire)

Donc,Problème de référence croisée (référence circulaire) de pointeur intelligent important Qu'est-ce que c'est alors?Venez jeter un oeil

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	shared_ptr<B> _ptrb;	// 指向B对象的智能指针
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	shared_ptr<A> _ptra;	// 指向A对象的智能指针
};

int main()
{
	shared_ptr<A> pa(new A());	// pa指向A对象,A的引用计数为1
	shared_ptr<B> pb(new B());	// pb指向B对象,B的引用计数为1
	pa->_ptrb = pb;	// A对象的成员变量_ptrb也指向B对象,B的引用计数为2
	pb->_ptra = pa;	// B对象的成员变量_ptra也指向A对象,A的引用计数为2

	cout << pa.use_count() << endl;
	cout << pb.use_count() << endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

résultat de l'opération :

A()
B()
2
2
  • 1
  • 2
  • 3
  • 4

Comme vous pouvez le constater, A et B ne sont pas détruits, ce qui signifie que les références croisées entraînerontnewLes ressources qui en sortent ne peuvent pas être libérées, ce qui entraîne une fuite de ressources !

analyser:
Insérer la description de l'image iciInsérer la description de l'image ici
dehorsmainportée de la fonction,paetpbDeux objets locaux sont détruits et les comptes de référence de l'objet A et de l'objet B sont réduits de 2 à 1 respectivement. Les conditions de libération de A et B ne peuvent pas être remplies (la condition de libération est que les comptes de référence de A et B soient réduits. à 0), provoquant ainsi deuxnewLes objets A et B sortis ne peuvent pas être libérés, provoquant des fuites de mémoire.Problème de référence croisée (référence circulaire) de pointeur intelligent important

Solution : utilisez des pointeurs intelligents puissants lors de la définition d'objets et utilisez des pointeurs intelligents faibles lors du référencement d'objets.

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	weak_ptr<B> _ptrb;	// 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	weak_ptr<A> _ptra;	// 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};


int main()
{
	// 定义对象时,用强智能指针
	shared_ptr<A> pa(new A());	// pa指向A对象,A的引用计数为1
	shared_ptr<B> pb(new B());	// pb指向B对象,B的引用计数为1

	// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
	pa->_ptrb = pb;
	// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
	pb->_ptra = pa;

	cout << pa.use_count() << endl; // 1
	cout << pb.use_count() << endl; // 1

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

résultat de l'opération :

A()
B()
1
1
~B()
~A()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

peut être vu, dehorsmainportée de la fonction,paetpbLes deux objets locaux sont détruits et les comptes de référence de l'objet A et de l'objet B respectivement sont réduits de 1 à 0, atteignant les conditions de libération de A et B. Par conséquent,newLes objets A et B qui sont sortis ont été détruits, ce qui a résolu le problème.Problème de référence croisée (référence circulaire) de pointeur intelligent important

Insérer la description de l'image ici
peut être vu,weak_ptrLes pointeurs intelligents faibles ne modifieront pas le nombre de références de la ressource, ce qui signifie que le pointeur intelligent faible observe uniquement si l'objet est vivant (si le nombre de références est 0) et ne peut pas utiliser la ressource.

Alors si à ce moment, ajoutez en classe Avoid testA() { cout << "非常好用的方法!!" << endl; }Une telle méthode ajoute à Bvoid func() { _ptra->testA(); }Est-il acceptable d'appeler cette méthode ?

  • Ne peut pas, car le pointeur intelligent faible n'est qu'un observateur et ne peut pas utiliser de ressources, c'est-à-dire qu'il ne fournit pas*et->Les fonctions surchargées des opérateurs ne peuvent pas utiliser des fonctions similaires aux pointeurs bruts

Solution:

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!!" << endl; }
	weak_ptr<B> _ptrb;	// 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	void func()
	{
		shared_ptr<A> sp = _ptra.lock(); // 提升方法,出函数作用域就自动析构了
		if (sp != nullptr)
			sp->testA();
	}
	weak_ptr<A> _ptra;	// 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};

int main()
{
	shared_ptr<A> pa(new A());
	shared_ptr<B> pb(new B());
	pa->_ptrb = pb;
	pb->_ptra = pa;
	cout << pa.use_count() << endl; // 1
	cout << pb.use_count() << endl; // 1

	pb->func();

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Bien que weak_ptrn'est pas propriétaire de l'objet, mais il peut le transmettrelock()la méthode tente d'obtenir un pointeur vers l'objetshared_ptr, si l'objet est toujours vivant (c'est-à-dire s'il existe d'autresshared_ptrmontrez-le),lock()renverra un pointeur vers l'objetshared_ptr; Sinon, une valeur vide sera renvoyéeshared_ptr

utiliserlock()Un scénario typique pour les méthodes est celui où un accès temporaire est requisweak_ptrl'objet pointé, et en même temps, je ne veux pas augmenter la durée de vie de l'objet

résultat de l'opération :

A()
B()
1
1
非常好用的方法!!
~B()
~A()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

À ce stade, vous pouvez voir qu’il a été appelé correctement !

4. Problèmes de sécurité des threads lorsque plusieurs threads accèdent à des objets partagés

Voyons-en unProblèmes de sécurité des threads lors de l'accès à des objets partagés à partir de plusieurs threads: Le thread A et le thread B accèdent à un objet partagé. Si le thread A détruit l'objet, le thread B doit appeler la méthode membre de l'objet partagé. À ce stade, le thread A a peut-être fini de détruire l'objet et le thread B ne le fera pas. Si vous essayez d'accéder à l'objet, une erreur inattendue se produira.

Regardez d’abord le code suivant :

class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!!" << endl; }
};

// 子线程
void handler01(A* q)
{
	// 睡眠两秒,此时main主线程已经把A对象给delete析构掉了
	std::this_thread::sleep_for(std::chrono::seconds(2));
	q->testA();
}

// main线程
int main()
{
	A* p = new A();
	thread t1(handler01, p);
	delete p;
	//阻塞当前线程,直到调用join()的线程结束
	t1.join();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

en exécutionq->testA();Lorsque cette déclaration est faite, lemainL'objet partagé a été détruit par le thread, ce qui est évidemment déraisonnable.

Si tu veux passerqSi le pointeur veut accéder à l'objet A, il doit déterminer si l'objet A est vivant. Si l'objet A est vivant, appelez.testAIl n'y a aucun problème avec la méthode ; si l'objet A a été détruit, appeleztestA a un problème !C'est-à-direqLorsque vous accédez à l'objet A, vous devez détecter si l'objet A est vivant. Comment résoudre ce problème ? Vous devez utiliser des pointeurs intelligents forts et faibles.

Regardons ce code :

// 子线程
void handler01(weak_ptr<A> wp)
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	shared_ptr<A> sp = wp.lock();
	if (sp != nullptr)
		sp->testA();
	else
		cout << "A对象已经析构,不能再访问!" << endl;
}

// main线程
int main()
{
	shared_ptr<A> p(new A());
	thread t1(handler01, weak_ptr<A>(p));
	t1.join();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

résultat de l'opération :

A()
非常好用的方法!!
~A()
  • 1
  • 2
  • 3

Vous pouvez voir qu'il fonctionnesp->testA();,parce quemainLe fil appelét1.join()La méthode attend la fin du thread enfant à ce moment-là.wppasserlockPromu avec succès àsp

Modifiez le code ci-dessus :

// 子线程
void handler01(weak_ptr<A> wp)
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	shared_ptr<A> sp = wp.lock();
	if (sp != nullptr)
		sp->testA();
	else
		cout << "A对象已经析构,不能再访问!" << endl;
}

// main线程
int main()
{
	{
		shared_ptr<A> p(new A());
		thread t1(handler01, weak_ptr<A>(p));
		t1.detach();
	}
	std::this_thread::sleep_for(std::chrono::seconds(3));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

résultat de l'opération :

A()
~A()
A对象已经析构,不能再访问!
  • 1
  • 2
  • 3

Comme vous pouvez le voir, nous définissons une portée et définissons égalementt1Pour séparer les threads, laissez les pointeurs intelligentspLorsque A est détruit hors de portée, il sera imprimé à ce momentA对象已经析构,不能再访问!, c'estwppasserlockÉchec de la mise à niveau verssp

Ce qui précède est le problème de sécurité des threads lorsque plusieurs threads accèdent à des objets partagés. Il s'agit d'une référence à.shared_ptretweak_ptrUne application typique.

5. Suppresseur de pointeur intelligent

6. Il est recommandé d'utiliser make_shared au lieu de shared_ptr


Article de référence :Compréhension approfondie des pointeurs intelligents C++