Résumé et arrangement des connaissances sur les pointeurs intelligents C++11

1. Le principe du pointeur intelligent

        Un pointeur intelligent est une classe qui stocke des pointeurs vers des objets alloués dynamiquement (tas). Il est utilisé pour le contrôle de la durée de vie et peut garantir que les objets alloués dynamiquement sont automatiquement et correctement détruits lorsqu'ils quittent la portée du pointeur pour éviter les fuites de mémoire. L'une de ses techniques de mise en œuvre courantes consiste à utiliser le comptage de références. A chaque utilisation, le compteur de références internes est augmenté de 1. A chaque destruction, le compteur de références internes est décrémenté de 1. Lorsqu'il est réduit à 0, le la mémoire de tas pointée est supprimée.

        C++11 fournit 3 types de pointeurs intelligents : std::shared_ptr, std::unique_ptr, std::weak_ptr, ajoutez <memory.h> lors de l'utilisation.

Deux, pointeur shared_ptr

        std::shared_ptr utilise le comptage de références, chaque copie de shared_ptr pointe vers la même mémoire et la mémoire ne sera pas libérée tant que le dernier shared_ptr ne sera pas détruit.

1. Initialisation

1) Initialiser par le constructeur

std ::shared_ptr<int> p(new int(1));

std::shared_ptr<int> p2 = p ;

2) Initialisé par std :: make_shared<T>

std ::shared_ptr<int> p = std::make_shared<int>(10);

3) Initialiser par la méthode de réinitialisation

std::shared_ptr<int>ptr ;
ptr.reset(nouveau int(1));
if(ptr)
{     std::cout<< "ptr n'est pas nul" ; }

2. Obtenez le pointeur d'origine

Lorsque vous avez besoin d'obtenir le pointeur brut, vous pouvez utiliser la méthode get pour obtenir le pointeur brut

std ::shared_ptr<int> ptr(new int(1));
int *p = ptr.get();

3. Spécifiez le suppresseur

L'initialisation du pointeur intelligent peut spécifier un effaceur, comme suit :

void DeleteInitPtr(int* p)
{     supprimer p; }

std ::shared_ptr<int> p(new int, DeleteInitPtr);

Lorsque le nombre de références de p est 0, le suppresseur DeleteInitPtr est automatiquement appelé pour libérer la mémoire de l'objet. Le suppresseur peut également être écrit sous la forme d'une expression lambda, il peut donc être réécrit comme suit :

std ::shared_ptr<int> p(new int, [](int* p){delete p;});

(1) Lorsque std::shared_ptr gère des tableaux dynamiques, il faut aussi spécifier le deleter, car std::shared_ptr ne supporte pas les objets tableau par défaut, le code
est le suivant :
    std::shared_ptr<int> p(new int[10], [] (int* p){supprimer[] p;});

(2) Gérez les tableaux via std :: default_delete en tant que suppresseur :
std ::shared_ptr<int> p(new int[10], std ::default_delete<int[]>);

(3)通过make_shared_array让std::shared_ptr支持数组:
template<typename T>
shared_ptr<T> make_shared_array(size_t size)
{     return std::shared_ptr<int> p(new T(size), std::default_delete<T []>()); }

测试代码:
std::shared_ptr<int> p = make_shared_array<int>(10);
std ::shared_ptr<char> p = make_shared_array<char>(10);

4. Problèmes nécessitant une attention particulière lors de l'utilisation de shared_ptr

Bien que le pointeur intelligent puisse gérer automatiquement la mémoire de tas, il présente encore de nombreux pièges, vous devez donc y prêter attention lorsque vous l'utilisez.

1) N'initialisez pas plusieurs shared_ptr avec un pointeur brut, par exemple les éléments suivants sont faux :

int *ptr = nouvel entier ;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr) ; erreur de logique

2) Ne créez pas shared_ptr dans les arguments de la fonction. Comme suit :
function(shared_ptr<int>(new int), g());

Cela est dû aux différentes conventions d'appel des différents compilateurs, ce qui peut provoquer des fuites de mémoire. La bonne façon d'écrire :
shared_ptr<int> p(new int());
f(p, g());

3) Renvoyez le pointeur via shared_from_this()

Ne renvoyez pas this en tant que shared_ptr, car le pointeur this est essentiellement un pointeur nu, ce qui peut entraîner une destruction répétée, comme suit :

struct A
{     shared_ptr<A>GetSelf()     {             return shared_ptr<S>(this); // ne fais pas ça     } } ;




int main()
{     shared_ptr<A> sp1(new A);     shared_ptr<A> sp2 = sp1->GetSelf();     renvoie 0 ; }



Dans cet exemple, deux pointeurs intelligents sp1 et sp2 sont construits avec le même pointeur (ceci), et il n'y a aucune relation entre eux. Après avoir quitté la portée, celui-ci sera détruit par les deux pointeurs intelligents construits respectivement. Une erreur qui provoque
des destructeurs.
Comment renvoyer correctement le shared_ptr de this : laissez la classe cible dériver la classe std :: enable_shared_from_this<T>, puis utilisez la fonction membre accumulée shared_from_this pour
renvoyer le shared_ptr de this, comme suit :
classe A : public std :: enable_shared_from_this <A>
{     std : :shared_ptr<A> GetSelf()     {         return shared_froml_this();     } } ;




std ::shared_ptr<A> espion(nouveau A);
std ::shared_ptr<A> p = spy->GetSelf();

4) Pour éviter les références circulaires :
struct A,
struct B ;

struct A
{     std::shared_ptr<B> bptr ;     ~A() { cout << "A est supprimé !" << finl; } ; } ;


struct B
{     std::shared_ptr<A> aptr ;     ~B() { cout << "B est supprimé !" << finl; } ; } ;


void testPtr()
{     std::shared_ptr<A> ap(new A);     std ::shared_ptr<B> bp(nouveau B);     ap->bptr = pb ;     pb->aptr = ap ; }




Le résultat du test est que les deux pointeurs A et B ne seront pas supprimés et qu'il y a une fuite de mémoire. La référence circulaire fait que les comptes de référence de ap et bp sont de 2. Après avoir quitté l'oscilloscope, les comptes de référence sont réduits à 1 et ne sera pas réduit à 0, ce qui entraînera Les deux pointeurs ne seront pas détruits, ce qui entraînera une fuite de mémoire. La solution consiste à changer le type de pointeur intelligent de tout membre de A et B en std::weak_ptr.

Trois, pointeur unique_ptr

        unique_ptr est un pointeur intelligent exclusif qui ne permet pas à d'autres pointeurs intelligents de partager ses pointeurs internes.

Caractéristiques:

1. Il n'est pas permis d'affecter un unique_ptr à un autre unique_ptr par affectation. comme:

unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = myPtr; // erreur, impossible de copier

2. La propriété Unique_ptr peut être transférée via std :: move

unique_ptr<T> monPtr(nouveau T);
unique_ptr<T> monAutrePtr = std::move(monPtr);

3. Unique_ptr ne peut pas utiliser la méthode make_shared pour créer un pointeur intelligent. C11 n'a actuellement pas de méthode make_unique

4. Unique_ptr peut pointer vers un tableau, tel que :

std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9; //Mettre le dernier élément à 9

5. Unique_ptr spécifie le suppresseur

Unique_ptr doit déterminer le type de suppression lors de la spécification de la suppression, vous ne pouvez donc pas spécifier directement la suppression comme shared_ptr, vous pouvez l'écrire comme ceci :
std::unique_ptr<int, void(*)(int*)> ptr(nwe int( 1), [ ](int*p){delete p;}); Cette façon d'écrire est correcte lorsque l'expression lambdb ne capture pas la variable, et si la variable est capturée, le compilateur signalera une erreur.
Si vous souhaitez que le suppresseur de unique_ptr prenne en charge les expressions lambda, vous pouvez écrire :
std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int*p ) {supprimer p
 ;
}
 )
;

struct MyDeleter
{         void operator() (int*p)         {                 cout<<"delete"<<endl ;                 supprimer p ;         } } ;





int main()
{         std::unique_ptr<int, MyDeleter> p(new int(1));         renvoie 0 ; }


Quatre, pointeur de référence faible faible_ptr

        Le pointeur de référence faible faible_ptr est utilisé pour surveiller shared_ptr, et n'augmentera pas le nombre de références de 1. Il ne gère pas le pointeur interne de shared_ptr, principalement pour surveiller le cycle de vie de shared_ptr, plus comme un assistant de shared_ptr. Weak_ptr n'a pas d'opérateurs surchargés * et ->, car il ne partage pas que de la haine, et ne peut pas exploiter les ressources, principalement via le droit de surveillance des ressources expirées de shared_ptr, sa construction et sa destruction ne provoqueront pas l'augmentation et la diminution du nombre de références. Le but est d'observer l'existence de ressources de pointeurs partagées.

1. Utilisez la méthode use_count() pour obtenir le nombre de références de la ressource d'observation actuelle, comme suit :

shared_ptr<int> sp(new int(10));
faible_ptr<int> wp(sp);

cout<<wp.use_count()<<endl;

2. Utilisez la méthode expired() pour déterminer si la ressource observée a été libérée

shared_ptr<int> sp(new int(10));
faible_ptr<int> wp(sp);

if(wp.expired())
{     cout<<"weak_ptr est invalide, la ressource surveillée a été libérée !\n"<<endl; } else {     cout<<"weak_ptr est valide"<<endl; }





3. Obtenez le shared_ptr surveillé via la méthode de verrouillage, comme suit :

std ::weak_ptr<int> gw ;
void f()
{     if(gw.expired())     {         std::cout<<"gw a expiré\n" ;     }     else     {         auto spt = gw.lock();         std::cout <<*spt << "\n" ;     } }









int main()
{     {         auto sp = std::make_shared_ptr<int>(42);         gw = sp;         f();     }     f(); } La sortie est la suivante : 42 gw a expiré









4. low_ptr renvoie ce pointeur

Comme mentionné précédemment, le pointeur this ne peut pas être renvoyé directement en tant que shared_ptr. Vous devez renvoyer le pointeur intelligent via la classe std :: enable_shared_from_this et sa méthode shared_from_this. La raison en est qu'il existe un pointeur faible_ptr dans la classe std :: enable_shared_from_this. Ce pointeur_faible est utilisé pour observer Pour ce pointeur intelligent, lorsque la méthode shared_from_this() est appelée, la méthode lock() du pointeur_faible interne sera appelée et le pointeur_partagé observé sera renvoyé.

Revoyez l'exemple précédent :

classe A : public std::enable_shared_from_this<A>
{     std::shared_ptr<A> GetSelf()     {         return shared_froml_this();     } } ;




std ::shared_ptr<A> espion(nouveau A);
std ::shared_ptr<A> p = spy->GetSelf();

Il est prudent de créer le pointeur intelligent de l'objet A à l'extérieur et de renvoyer le pointeur intelligent de this via l'objet, car shared_from_this() est le pointeur intelligent renvoyé après que low_ptr a appelé la méthode lock().

5. faiblesse_ptr résout le problème de référence circulaire

structure A ;
structure B ;

struct A
{     std::shared_ptr<B> bptr ;     ~A() { cout << "A est supprimé !" << finl; } ; } ;


struct B
{     std::shared_ptr<A> aptr ;     ~B() { cout << "B est supprimé !" << finl; } ; } ;


void testPtr()
{     std::shared_ptr<A> ap(new A);     std ::shared_ptr<B> bp(nouveau B);     ap->bptr = pb ;     pb->aptr = ap ; }




Dans l'exemple ci-dessus, en raison de références circulaires, les décomptes de références de ap et bp sont tous les deux 2. Après avoir quitté la portée, les décomptes de références sont réduits à 1 et le pointeur ne sera pas supprimé, ce qui entraînera des fuites de mémoire. Ce problème peut être résolu par faiblesse_ptr, il suffit de changer n'importe quel membre de A ou B en faiblesse_ptr.

structure A ;
structure B ;

struct A
{     std::shared_ptr<B> bptr ;     ~A() { cout << "A est supprimé !" << finl; } ; } ;


struct B
{     std ::weak_ptr<A> aptr ;     ~B() { cout << "B est supprimé !" << finl; } ; } ;


void testPtr()
{     std::shared_ptr<A> ap(new A);     std ::shared_ptr<B> bp(nouveau B);     ap->bptr = pb ;     pb->aptr = ap ; }




De cette façon, lors de l'attribution d'une valeur à un membre de B, c'est-à-dire lors de l'exécution de bp->aptr=ap;, puisque aptr est faible_ptr, il n'augmentera pas le nombre de références, donc le nombre de références de ap sera toujours 1 . Après avoir quitté la portée, ap Le nombre de références de l'objet sera réduit à 0 et le pointeur A sera détruit. Après la destruction, le nombre de références du bptr à l'intérieur sera réduit à 1, puis la référence bp compte sera réduit de 1 à 0 après avoir quitté la portée, et l'objet B sera également détruit, aucune fuite de mémoire ne se produira.

Résumé : Les pointeurs intelligents sont un outil puissant pour résoudre d'éventuels problèmes de fuite de mémoire pour les langages sans mécanismes de récupération de place, mais il y a certains points auxquels il faut prêter attention dans l'utilisation réelle. Heureusement, ces problèmes peuvent être résolus.

1) Comment choisir entre shared_ptr et unique_ptr : si vous souhaitez qu'un seul pointeur gère les ressources ou les tableaux, vous pouvez utiliser unique_ptr ; si vous souhaitez que plusieurs pointeurs intelligents gèrent la même ressource, vous pouvez utiliser shared_ptr.

2) low_ptr est un assistant de shared_ptr, il surveille simplement si les ressources gérées par shared_ptr sont libérées, et n'exploite et ne gère pas les ressources lui-même. Il est utilisé pour résoudre le problème de référence circulaire de shared_ptr et le problème de renvoi de ce pointeur.

Je suppose que tu aimes

Origine blog.csdn.net/leiyang2014/article/details/128370105
conseillé
Classement