* ne peut contenir que des objets de type *A*.
Le compilateur acceptera de stocker un objet de type *B*, mais uniquement en le tronquant pour le convertir vers le type *A*,
démarche plutôt expéditive !
La solution consiste à utiliser un vector de pointeurs vers des objets de type *A*, le type de la classe mère.
Dans ce cas précis, lorsqu'un pointeur est utilisé, le langage vérifie dynamiquement à l'exécution le type réel de l'objet.
Ainsi, tant que vous utilisez l’héritage, vous ne pouvez pas bannir complètement les pointeurs.
En revanche, dans l’esprit du C++ moderne, il est recommandé de les gérer via des smart pointers.
Ceux-ci combinent la performance du C++ avec une gestion mémoire plus sûre, comparable à celle d’un garbage collector en Java ou C#.
Les shared_ptr
==============
Nous rappelons que :
.. panels::
:column: col-lg-5 p-2
.. raw:: html
Les mots-clefs new et delete SONT INTERDITS
.. raw:: html
Les fonctions malloc et free SONT INTERDITES
Si les opérations new/delete ne sont pas autorisées, alors comment que faire ?
Nous allons vous présenter une nouvelle approche appelée **pointeurs intelligents** (**smart pointers**).
Nous nous concentrons plus particulièrement sur les **shared pointers** qui allient l'efficacité des pointeurs avec la sécurité d'un garbage collector.
En effet, un *shared_ptr* est un pointeur doublé d'un **compteur d'utilisation** (**use count**) vers l'entité désignée.
Lorsque ce compteur atteint 0, alors le *shared_ptr* déclenche la destruction de l'objet pointé comme le ferait un garbage collector.
Shared_ptr comme arguments
==========================
Pour instancier un objet de type *T* et l'associer à un *shared pointer* *p*, vous **devez** utiliser la syntaxe suivante :
.. panels::
:column: col-lg-9 p-2
| shared_ptr p = **make_shared** (paramètres du constructeur);
| ou
| **auto** p = make_shared(paramètres du constructeur);
Le mot clef *auto* permet de simplifier la syntaxe des instanciations.
Pour accéder aux membres de l'objet associé au smart pointeur, il faut utiliser l'opérateur flèche **->** i.e. la notation classique des pointeurs.
Voici un exemple ci-dessous :
.. code-block::
#include
#include
using namespace std;
struct Point
{
int x, y;
Point(int a, int b) : x(a), y(b) { }
void Aff() { cout << "(" << x << "," << y << ")"; }
};
int main()
{
shared_ptr p1 = make_shared(1, 2);
p1->Aff();
auto p2 = make_shared(4, 5);
p2->Aff();
}
>> (1,2)
>> (4,5)
.. warning:: Nous vous demandons d'utiliser les *shared_ptr* car ils constituent le bagage minimum d'un programmeur C++ moderne.
Est-ce que les shared_ptr résolvent tous les problèmes de libération de mémoire ? Malheureusement non.
Par exemple, nous pouvons citer le cas des références cycliques : un objet *K* ayant un shared_ptr vers un objet *M* contenant lui-même un shared_ptr vers l'objet *K*.
Le mécanisme des shared_ptr reste impuissant car chaque compteur d'utilisation ne pourra pas atteindre 0 à cause du cycle.
Nous vous conseillons d'éviter cette situation.
Shared_ptr et arguments
=======================
Voici un exemple qui indique comment un *shared_ptr* se comporte lors des appels de fonction :
.. code-block:: cpp
#include
#include
using namespace std;
struct Point
{
int x, y;
Point(int a, int b) : x(a), y(b) { cout << "Point created\n"; }
~Point() { cout << "Point destroyed\n"; }
void Print() { cout << "(" << x << "," << y << ")" << endl; }
};
void test_1(shared_ptr & p1)
{ // pass by reference
5 cout << "test_1 use count : " << p1.use_count() << "\n";
}
void test_2(shared_ptr p2)
{ // pass by copy
7 cout << "test_2 use count : " << p2.use_count() << "\n";
}
int main()
{
1 auto p = make_shared(1, 2); // instantiation
2 p->Print();
3 cout << "use count : " << p.use_count() << "\n";
4 test_1(p);
6 test_2(p);
8 cout << "use count : " << p.use_count() << "\n";
}
1 >> Point created
2 >> (1,2)
3 >> use count : 1
5 >> test_1 use count : 1
7 >> test_2 use count : 2
8 >> use count : 1
9 >> Point destroyed
Description ligne à ligne :
* 1 : *auto p = make_shared(1, 2)* : crée un objet de type *Point* et un *shared_ptr p* pointant sur cet objet.
* 2 : *p->Aff();* : affiche les coordonnées *(x,y)* du point en question.
* 3 : *cout << "use count"* : un seul *shared_ptr* point vers l'objet Point, le compteur est à 1.
* 4 : *test_1(p)* : appel de la fonction *test_1*, le shared_ptr est passé par référence.
* 5 : *cout << "test_1"*, il n'y a pas eu de nouveau pointeur créé, le compteur est toujours égal à 1.
* 6 : *test_2(p)* : appel de la fonction *test_2*, le *shared_ptr* est passé par copie, un nouveau *shared_ptr p2* est créé et désigne le même objet.
* 7 : *cout << "test_2"*, les deux *shared_ptr* *p* et *p2* pointent vers le même objet, le compteur vaut donc 2.
* 8 : *cout << "use count"*, au retour dans la fonction *test_2*, le *shared_ptr p2* a été détruit, le compteur retombe à 1.
* 9 : fin de la fonction *main()*, le *shared_ptr p* est détruit, le compteur de tombe à 0, et l'objet *Point* est détruit automatiquement.
Shared_ptr et vector
====================
Voici un exemple qui indique comment un *shared_ptr* se comporte avec des *vector* :
.. code-block:: cpp
#include
#include
#include
using namespace std;
struct Point
{
int x, y;
Point(int a, int b) : x(a), y(b) { cout << "Point created : "; Print(); }
~Point() { cout << "Point destroyed: "; Print(); }
void Print() { cout << "(" << x << "," << y << ")" << endl; }
};
int main()
{
vector> L;
1 L.push_back(make_shared(1,2));
1 L.push_back(make_shared(3,4));
2 L.push_back(L[0]);
3 cout << "count L[0] : " << L[0].use_count() << "\n";
4 cout << "count L[1] : " << L[1].use_count() << "\n";
5 cout << "L[1] destroyed" << endl;
6 L.erase(L.begin()+1);
7 cout << "End of program" << endl;
}
1 >> Point created : (1,2)
1 >> Point created : (3,4)
3 >> use count L[0] : 2
4 >> count L[1] : 1
5 >> L[1] destroyed
6 >> Point destroyed: (3,4)
7 >> End of program
>> Point destroyed: (1,2)
Commentaires :
* 1 : Deux objets *Point* sont créés et insérés dans un *vector L*.
* 2 : Le *shared_ptr* *L[0]* est copié pour créer un 3ème élément dans la liste *L*.
* 3 : Le compteur d'utilisation vaut 2 car il y a deux *shared_ptr* pointant vers le même objet : *L[0]* et *L[2]*.
* 4 : Le compteur de *L[1]* vaut 1 car il y a un unique *shared_ptr* pointant vers cet objet.
* 5 : La ligne : *L.erase(L.begin()+1)* efface le *shared_ptr L[1]*, le compteur tombe à 0.
* 6 : Par conséquent le *Point : (3,4)* est détruit dans la foulée : *Point destroyed : (3,4)*
* 7 : La fonction *main* se termine, la liste *L* est détruite ainsi que les *shared_ptr* qu'elle contient.
* 8 : Le compteur d'utilisation vers l'objet *Point(1,2)* tombe à 0 et cet objet est automatiquement détruit : *Point destroyed : (1,2)*.
Quizzz
======
.. quiz:: smartp
:title: Smart pointers
* :quiz:`{"type":"FB","answer":"make_shared(3,5)"}` Syntaxe pour créer un shared pointer sur un objet de type *T* avec comme paramètres de construction 3 et 5. Ne pas écrire le ;
* :quiz:`{"type":"TF","answer":"F"}` Depuis un shared pointer *p*, pour accéder au paramètre *a*, dois-je écrire *p.a* ?
* :quiz:`{"type":"FB","answer":"1"}` Quelle est la valeur affichée par le code suivant :
.. code-block::
void test(shared_ptr p) { p->Aff(); }
int main()
{
shared_ptr p = make_shared(1,2);
for (int i = 0 ; i < 3 ; i++) test(p);
cout << p.use_count();
return 0;
}
* :quiz:`{"type":"TF","answer":"F"}` Un shared pointer permet de libérer l'objet pointé dès que son compteur d'utilisation vaut -1.