Smart pointers ************** .. include:: ../BoutonGoogleTrad.rst Introduction ============ Le C++ moderne cherche autant que possible à éviter la manipulation directe des pointeurs. Les références, les conteneurs de la STL et de nombreux autres mécanismes permettent aujourd’hui d’écrire du code sans manipuler explicitement des adresses mémoire. Cependant, les allocations dynamiques restent nécessaires dans de nombreuses situations. Lors du chargement d’un objet 3D, par exemple, il peut être nécessaire de créer dynamiquement différentes ressources : textures, géométrie, animations, matériaux, etc.. Nous choisissons de ne pas travailler avec l'opérateur d'allocation dynamique du C++ (:cpp:`new`) car il est à l'origine de nombreuses fuite mémoire car il arrive que l'on oublie de libérer un objet. Pour s'assurer que les objets seront correctement libérées lorsqu’ils ne sont plus utilisés, le C++ a mis en place des :cppterm:`smart pointers` qui allient l'efficacité des pointeurs à la sécurité d'un garbage collector. Nous allons nous intéresser plus particulièrement aux :cpp:`shared pointers` qui permettent de partager une ressource entre plusieurs entités. Shared pointers =============== Principe -------- Les :cppterm:`shared_ptr` sont utilisés lorsqu’un objet est partagé par plusieurs propriétaires. Ainsi chaque propriétaire dispose d'un :cpp:`shared_ptr` lui permettant d'accéder à cet objet. En parallèle est maintenu un compteur unique indiquant le nombre de propriétaires faisant référence à cet objet. Lorsque le dernier :cpp:`shared_ptr` disparaît, le compteur atteint zéro et l’objet est automatiquement détruit. Syntaxe ------- Pour instancier un objet de type :cpp:`T` et l'associer à un :cpp:`shared pointer` :cpp:`p`, vous **devez** utiliser la syntaxe suivante : .. panels:: :column: col-lg-9 p-2 | :cpp:`shared_ptr p = make_shared(constructor parameters);` | ou | :cpp:`auto p = make_shared(constructor parameters);` Le mot clef :cpp:`auto` permet de simplifier la syntaxe des instanciations. Opérateur flèche ---------------- Pour accéder aux membres d'un objet associé à un 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() { auto p1 = make_shared(4, 5); p1->Aff(); } >> (4,5) .. warning:: Nous vous demandons d'utiliser les :cpp:`shared_ptr` car ils constituent le bagage minimum d'un programmeur C++ moderne. Est-ce que les :cpp:`shared_ptr` résolvent tous les problèmes de libération de mémoire ? Malheureusement non. Prenons un exemple : * Un objet :cpp:`A` possède un :cpp:`shared_ptr` vers un objet :cpp:`B` * Un objet :cpp:`B` possède un :cpp:`shared_ptr` vers un objet :cpp:`A` Nous sommes face à une dépendance cyclique et dans ce cas, le mécanisme des :cpp:`shared_ptr` reste impuissant. En effet, même si aucune association existe dans le programme vers l'objet :cpp:`A` et l'objet :cpp:`B`, chaque compteur d'utilisation reste bloqué à 1 à cause du cycle. Ainsi, aucun de ces deux objets ne pourra être libéré. Nous vous conseillons d'éviter cette situation dès la conception. Comptage ======== Présentation ------------ Que se passe-t-il lorsque plusieurs :cpp:`shared_ptr` sont associés au même objet ? Regardons comment le compteur de références évolue : .. code-block:: cpp #include #include using namespace std; struct A {}; int main() { auto p1 = make_shared(); // instantiation cout << "number of shared ptr : " << p1.use_count() << endl; auto p2 = p1; // new shared ptr cout << "number of shared ptr : " << p1.use_count() << endl; cout << "number of shared ptr : " << p2.use_count() << endl; } >> 1 >> 2 >> 2 Description ligne à ligne : * :cpp:`auto p1 = make_shared()` : crée un objet de type :cpp:`A` et un :cpp:`shared_ptr` associé à cet objet. * :cpp:`auto p2 = p1;` : crée un nouveau :cpp:`shared_ptr` associé au même objet. * :cpp:`p1.use_count()`: le compteur est égal à 2 car 2 :cpp:`shared_ptr` différents sont associés au même objet. * :cpp:`p2.use_count()`: le compteur vaut aussi 2 car ces deux :cpp:`shared_ptr` sont liés au même objet. Quizzz ====== .. quiz:: smartp :title: Smart pointers * :quiz:`{"type":"FB","answer":"make_shared()"}` Donnez la syntaxe pour créer un shared pointer sur un objet de type :cpp:`T`. Ne pas écrire le ; * :quiz:`{"type":"FB","answer":"p->a"}` Donnez la syntaxe pour accéder au paramètre :cpp:`a` depuis un shared pointer :cpp:`p`. * :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 :cpp:`shared_ptr` permet de libérer l'objet pointé dès que son compteur d'utilisation vaut -1.