Comprendre les pointeurs intelligents en C++

Mohd Mohtashim Nawaz 12 octobre 2023
  1. Implémenter un pointeur intelligent en C++
  2. Utiliser Smart Pointer Destructor pour éviter les fuites de mémoire en C++
  3. Types de pointeurs intelligents en C++
  4. Quand utiliser les pointeurs intelligents en C++
Comprendre les pointeurs intelligents en C++

Cet article traitera des pointeurs intelligents en C++, de la manière dont ils empêchent les fuites de mémoire, des types de pointeurs intelligents et des cas dans lesquels nous devrions les utiliser.

Implémenter un pointeur intelligent en C++

Nous pouvons définir le pointeur intelligent en C++ comme un modèle de classe que nous pouvons utiliser pour maintenir le pointeur brut d’origine. Notre classe de pointeur intelligent contient une variable de pointeur pour contenir nos méthodes de remplacement de pointeur brut, de destructeur et d’opérateur.

Cependant, nous ne sommes pas limités à inclure uniquement ces champs et méthodes. Nous pouvons ajouter nos méthodes selon nos besoins.

Au lieu de gérer directement le pointeur brut, nous utilisons cette classe pour gérer le pointeur. Et au lieu de définir une classe personnalisée pour les pointeurs intelligents en C++, nous pouvons également utiliser la bibliothèque C++ standard.

Définissons une classe personnalisée pour l’implémentation des pointeurs intelligents C++.

#include <iostream>

using namespace std;

template <class T>
class CustomSmartPointer {
  T *data;

 public:
  explicit CustomSmartPointer(T *ptr = NULL)  // Constructor to assign pointer
  {
    data = ptr;
  }
  ~CustomSmartPointer()  // Destructor that deallocated memory
  {
    delete data;
  }
  // We overload * and -> operators
  T *operator->() { return data; }
  T &operator*() { return *data; }
};

int main() {
  CustomSmartPointer<int> myPtr(new int());
  *myPtr = 100;
  cout << *myPtr << endl;
  // After executing above statement, memory allocated to myPtr is deallocated.
  return 0;
}

Production:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ custom_smart_prt.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
100

Utiliser Smart Pointer Destructor pour éviter les fuites de mémoire en C++

La présence d’un destructeur rend notre classe de pointeurs intelligents spéciale et la distingue des pointeurs bruts. Nous utilisons le destructeur de pointeur intelligent C++ pour libérer la mémoire affectée à notre pointeur.

Un destructeur est automatiquement appelé et libère la mémoire affectée lorsqu’un objet de classe sort de la portée. Si nous avons automatisé le processus de désallocation de mémoire, nous n’avons pas à nous soucier des fuites de ressources causées par les pointeurs que nous pourrions oublier de désallouer.

Le destructeur de pointeur intelligent agit comme un ramasse-miettes automatique en C++, similaire à Java et C#.

Voyons un exemple de fuite de ressources causée par des pointeurs bruts.

#include <iostream>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  while (true) {
    student* p = new student;
    *p->name = "Stark";
    *p->marks = 100;
  }
  return 0;
}

Comme nous pouvons le voir, nous initialisons le pointeur de type étudiant dans une boucle infinie. Nous n’avons pas désalloué la mémoire du pointeur, donc cela continuerait à allouer les ressources jusqu’à ce que toute la mémoire soit occupée.

Nous ne devrions pas exécuter ce programme longtemps. Sinon, notre système pourrait se bloquer en raison d’un débordement de mémoire.

Cependant, si nous utilisions le pointeur intelligent, la mémoire aurait été automatiquement désallouée après chaque boucle. Ainsi à la fois, une seule mémoire de pointeur aurait été occupée.

Types de pointeurs intelligents en C++

Nous avons trois types différents de pointeurs intelligents en C++. Ce sont les types implémentés dans la bibliothèque C++ pour les pointeurs intelligents.

unique_ptr

Ce type de pointeur intelligent nous permet d’affecter un seul utilisateur au pointeur brut sous-jacent. Cela signifie que nous ne pouvons pas affecter le même pointeur sous-jacent à deux objets ou plus.

Nous devrions utiliser ce type de pointeur par défaut jusqu’à ce que nous puissions partager la mémoire.

shared_ptr

Comme son nom l’indique, nous pouvons attribuer plusieurs propriétaires à un pointeur brut à l’aide de ce pointeur intelligent. Cela utilise un compteur de référence pour garder une trace du nombre de destinataires.

weak_ptr

Ce type de pointeur intelligent est très similaire au shared_ptr, sauf qu’il ne participe pas au compteur de référence. Cela ne signifie pas qu’il n’a pas de compteur de référence, mais sa référence n’est pas comptée.

Au lieu de cela, nous utilisons son compteur de références pour compter d’autres références partagées. Nous pouvons utiliser ce pointeur lorsque nous n’avons pas besoin d’un contrôle strict sur la propriété.

Il donne accès à un ou plusieurs objets shared_ptr. Cependant, il a un avantage car il soulage la possibilité de blocage.

Voyons une implémentation de pointeur intelligent C++.

#include <iostream>
#include <memory>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  unique_ptr<student> ptr1(new student);
  ptr1->setName("Stark");
  ptr1->setMarks(100);
  cout << "unique_ptr output >>\n";
  cout << ptr1->getName() << " : " << ptr1->getMarks() << "\n";

  cout << "shared_ptr output >> \n";
  shared_ptr<student> ptr2(new student);
  ptr2->setName("Tony");
  ptr2->setMarks(99);
  cout << ptr2->getName() << " : " << ptr2->getMarks() << "\n";

  shared_ptr<student> ptr22;
  ptr22 = ptr2;
  cout << ptr22->getName() << " : " << ptr22->getMarks() << "\n";
  cout << "Reference count of shared_ptr: " << ptr2.use_count() << endl;

  auto ptr = make_shared<student>();
  ptr->setName("Jarvis");
  ptr->setMarks(98);

  cout << "Weak pointer output >>" << endl;
  weak_ptr<student> ptr3;
  ptr3 = ptr;
  cout << "Reference count of weak_ptr: " << ptr3.use_count() << endl;
  shared_ptr<student> ref = ptr3.lock();
  cout << ref->getName() << " : " << ref->getMarks() << "\n";

  return 0;
}

Production:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ types_smart.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
unique_ptr output >>
Stark : 100
shared_ptr output >>
Tony : 99
Tony : 99
Reference count of shared_ptr: 2
Weak pointer output >>
Reference count of weak_ptr: 1
Jarvis : 98

Notons que le décompte de références du pointeur faible est 1 car il ne participe pas au décompte de références.

Quand utiliser les pointeurs intelligents en C++

Nous ne devrions pas utiliser des pointeurs intelligents pour tout. Puisque nous utilisons un modèle de classe pour les pointeurs intelligents, ils ont généralement des performances dégradées et ajoutent à la complexité du programme.

Nous devrions utiliser des pointeurs bruts pour les applications critiques en termes de performances et les petits programmes comme meilleure pratique. Cependant, nous devons toujours désallouer les ressources de pointeur après leur utilisation.

Si notre code est volumineux et qu’il est difficile de libérer manuellement de la mémoire pour chaque pointeur, nous devons utiliser les pointeurs intelligents. Nous devrions préférer utiliser des pointeurs intelligents si l’application n’est pas critique pour les performances.

Si nous devons gérer des exceptions, des décomptes de références, des exécutions de temps de désallocation, nous devons utiliser les pointeurs intelligents.

Article connexe - C++ Pointer