2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Inconvénients des pointeurs bruts :
delete
Libérer des ressources, provoquant une fuite de ressourcesdelete
Le programme s'est terminé normalement auparavant (par exemple, siif
milieureturn
) ou sortir anormalement avant qu’il ne soit trop tarddelete
, conduisant à une fuite de ressourcesÀ 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;
}
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 icip
Bien qu'il s'agisse d'un type de pointeur intelligent, il s'agit essentiellement d'un pointeur brut, doncp
Encore 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;
}
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); // 拷贝构造
Le code en cours d'exécution plante directement car le constructeur de copie par défaut effectue une copie superficielle.p1
etp2
tient la même chosenew int
Ressource,p2
La destruction libère d'abord les ressources, puisp1
Une fois détruit, il devientdelete
Pointeur sauvage, le programme plante
Alors, comment résoudre les problèmes causés par la copie superficielle ?récrireCSmartPtr
Jetez un oeil au constructeur de copie
CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
Maintenant, le code s'exécute normalement, mais à ce stadep1
etp2
Deux ressources différentes sont gérées. Si les utilisateurs ne les comprennent pas, ils penseront à tort que cela est le cas.p1
etp2
Gé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
auto_ptr
(C++98, désormais obsolète)scoped_ptr
(Bibliothèque Boost)unique_ptr
(C++11, recommandé)Incluez le fichier d'en-tête lorsque vous utilisez :#include <memory>
Considérons d'abord ce code
auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;
L’opération a échoué. Pourquoi ?auto_ptr
code source
Vous pouvez voir que la construction par copie appellera l'objet entrantrelease
méthode, cette méthode consiste à mettre_Right
La ressource pointée est renvoyée vers le nouveauauto_ptr
objet, et en même temps_Right
(vieuxauto_ptr
)de_Myptr
mis ànullptr
Bref, c'est remettre du vieuxauto_ptr
La ressource pointée est donnée au nouveauauto_ptr
Tenu, l'ancien est réglé surnullptr
.Par conséquent, le code ci-dessus utilise*ptr1
Tout simplement faux.(auto_ptr
Laissez toujours le dernier pointeur intelligent gérer la ressource)
Donc,auto_ptr
Peut-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;
}
Il est donc obsolète en C++auto_ptr
, à moins que le scénario d'application ne soit très simple.etauto_ptr
Obsolète en C++11 et entièrement supprimé en C++17
Résumer:auto_ptr
Les 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_ptr
sont réglés surnullptr
, ne laisse que le dernierauto_ptr
détenir des ressources
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_ptr
Code source:
template<class T> class scoped_ptr
{
private:
scoped_ptr(scoped_ptr const&);
scoped_ptr& operator=(scoped_ptr const&);
...
};
peut être vuscoped_ptr
Le 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_ptr
Il ne peut pas être utilisé dans des conteneurs. Si les conteneurs se copient ou s'attribuent des valeurs, cela entraînerascoped_ptr
Constructeur de copie d'objet et fonction d'affectation, erreur de compilation
scoped_ptr
etauto_ptr
La différence: Peut s'expliquer par la propriété,auto_ptr
La propriété des ressources peut être transférée à volonté, tandis quescoped_ptr
La propriété n'est pas transférée (car les constructeurs de copie et les fonctions d'affectation sont désactivés)
scoped_ptr
Généralement non utilisé
Jetez un oeil d'abordunique_ptr
Une 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;
};
Vue d'en haut,unique_ptr
un petit peuscoped_ptr
La 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_ptr
Effectuez 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_ptr
Fournit des constructeurs de copie et des fonctions d'affectation avec des paramètres de référence rvalue, c'est-à-direunique_ptr
Les pointeurs intelligents peuvent effectuer des opérations de construction de copie et d'affectation via des références rvalue, ou générerunique_ptr
Un emplacement pour des objets temporaires, par ex.unique_ptr
En 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=赋值重载函数
// 示例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;
}
Utilisez ensuiteunique_ptr
L'avantage est que les utilisateurs peuvent utiliser quelque chose commeunique_ptr<int> p2(move(p1));
déclaration, on voit clairement quep1
Ressources transférées àp2
,p1
Aucune ressource n'est retenue si vous l'utilisez.auto_ptr
Ce 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_ptr
Comme 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
Les pointeurs intelligents avec comptage de références incluent principalementshared_ptr
etweak_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.
Prenez ce qui a été mis en œuvre précédemmentCSmartPtr
Apportez 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; // 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl; // 20 20
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_ptr
etweak_ptr
C'est thread-safe !
shared_ptr
:puissantPointeurs intelligents (peuvent modifier le nombre de références des ressources)weak_ptr
:faiblePointeurs 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;
}
résultat de l'opération :
A()
B()
2
2
Comme vous pouvez le constater, A et B ne sont pas détruits, ce qui signifie que les références croisées entraînerontnew
Les ressources qui en sortent ne peuvent pas être libérées, ce qui entraîne une fuite de ressources !
analyser:
dehorsmain
portée de la fonction,pa
etpb
Deux 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 deuxnew
Les 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;
}
résultat de l'opération :
A()
B()
1
1
~B()
~A()
peut être vu, dehorsmain
portée de la fonction,pa
etpb
Les 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,new
Les 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
peut être vu,weak_ptr
Les 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 ?
*
et->
Les fonctions surchargées des opérateurs ne peuvent pas utiliser des fonctions similaires aux pointeurs brutsSolution:
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;
}
Bien que
weak_ptr
n'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_ptr
montrez-le),lock()
renverra un pointeur vers l'objetshared_ptr
; Sinon, une valeur vide sera renvoyéeshared_ptr
utiliser
lock()
Un scénario typique pour les méthodes est celui où un accès temporaire est requisweak_ptr
l'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()
À ce stade, vous pouvez voir qu’il a été appelé correctement !
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;
}
en exécutionq->testA();
Lorsque cette déclaration est faite, lemain
L'objet partagé a été détruit par le thread, ce qui est évidemment déraisonnable.
Si tu veux passerq
Si 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.testA
Il 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-à-direq
Lorsque 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;
}
résultat de l'opération :
A()
非常好用的方法!!
~A()
Vous pouvez voir qu'il fonctionnesp->testA();
,parce quemain
Le fil appelét1.join()
La méthode attend la fin du thread enfant à ce moment-là.wp
passerlock
Promu 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;
}
résultat de l'opération :
A()
~A()
A对象已经析构,不能再访问!
Comme vous pouvez le voir, nous définissons une portée et définissons égalementt1
Pour séparer les threads, laissez les pointeurs intelligentsp
Lorsque A est détruit hors de portée, il sera imprimé à ce momentA对象已经析构,不能再访问!
, c'estwp
passerlock
É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_ptr
etweak_ptr
Une application typique.
Article de référence :Compréhension approfondie des pointeurs intelligents C++