Programmation • IoT

Chapitre 4 – std::unique_ptr en pratique

Document réservé

Vous consultez actuellement la présentation publique de ce chapitre.

Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.

Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).

4 std::unique_ptr en pratique 4.6 Passer un unique_ptr à une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 La fonction ne devient pas propriétaire . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.2 La fonction devient propriétaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.7 Retourner un unique_ptr depuis une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . 4.7.1 Retourner une variable locale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.10.1 get() et release() ne font pas la même chose . . . . . . . . . . . . . . . . . . . . 4.11 unique_ptr et tableaux dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.11.1 Mais faut-il utiliser unique_ptr<T[]> ? . . . . . . . . . . . . . . . . . . . . . . . . . 4.12 Faut-il toujours utiliser un unique_ptr ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.13 unique_ptr et pointeur brut : deux rôles différents . . . . . . . . . . . . . . . . . . . . . . . 2026 – C++ Partie III std::unique_ptr en pratique
Accueil
std::unique_ptr en pratique Retour à std::unique_ptr Nous avons introduit std::unique_ptr comme un outil permettant d’appliquer le principe RAII à une ressource possédant un propriétaire unique, cependant nous avions rencontré une difficulté : auto p1 = std::make_unique<int>(42); Cette copie est interdite. Un unique_ptr représente une propriété exclusive, deux unique_ptr ne doivent donc pas devenir propriétaires de la même ressource. Cette interdiction nous a conduits à étudier la sémantique de déplacement que nous pouvons maintenant comprendre et utiliser correctement : auto p2 = std::move(p1); La propriété n’est pas copiée mais transférée. Créer un unique_ptr Pour utiliser std::unique_ptr, nous devons inclure : Nous avons déjà rencontré l’écriture std::unique_ptr<int> p(new int(42)); qui certes, est valide, mais en C++ moderne nous préférerons généralement : auto p = std::make_unique<int>(42); La fonction std::make_unique construit l’objet et retourne un unique_ptr qui en devient immédiatement propriétaire (l’utilisation de auto évite ici de répéter le type). Un objet d’une classe 2026 – C++ Partie III Point(double x0, double y0) std::unique_ptr en pratique Nous pouvons créer dynamiquement un Point auto p = std::make_unique<Point>(3.0, 4.0); Le type déduit de p est : std::unique_ptr<Point> et p devient l’unique propriétaire du Point. Utiliser l’objet possédé Un unique_ptr fournit les opérateurs habituels permettant d’accéder à l’objet qu’il possède. auto p = std::make_unique<int>(42); std::cout << *p << ’\n’; donne accès à l’objet possédé, pour un objet : auto p = std::make_unique<Point>(3.0, 4.0); std::cout << p->getX() << ’\n’; std::cout << p->getY() << ’\n’; L’opérateur -> fonctionne donc d’une manière similaire à celle d’un pointeur classique donc, la différence fondamentale entre un pointeur brut et un unique_ptr ne réside pas principalement dans la manière d’accéder à l’objet mais elle réside dans la propriété et la gestion de sa durée de vie. Transférer la propriété auto p1 = std::make_unique<int>(42);
Accueil
2026 – C++ Partie III std::unique_ptr en pratique Nous ne pouvons pas écrire : car cela demanderait une copie. auto p2 = std::move(p1); Nous savons maintenant exactement pourquoi, l’expression permet au constructeur de déplacement de unique_ptr d’être utilisé ainsi la propriété de la ressource est Dans le cas de unique_ptr, l’état de l’objet source après déplacement est précisément défini : il devient Tester un unique_ptr Un unique_ptr ne possède pas nécessairement une ressource. Nous pouvons par exemple déclarer std::unique_ptr<int> p; std::unique_ptr<int> p = nullptr; Dans les deux cas, p est vide. Nous pouvons le tester : std::cout << *p << ’\n’; La condition est vraie si p possède un objet. Nous pouvons également écrire :
Accueil
2026 – C++ Partie III std::unique_ptr en pratique std::cout << « Aucune ressource\n »; std::cout << « Aucune ressource\n »; • Avant de déréférencer un unique_ptr susceptible d’être vide, il faut vérifier qu’il possède effectivement un objet. constitue la forme la plus simple de ce test. Passer un unique_ptr à une fonction La notion de propriété devient particulièrement importante lorsqu’un unique_ptr est passé à une fonction. Il faut se poser une question avant même de choisir le type du paramètre, la question est : La fonction doit-elle devenir propriétaire de la ressource ? La réponse détermine la manière dont nous transmettrons l’objet. La fonction ne devient pas propriétaire Supposons qu’une fonction doive uniquement consulter un Point : void afficher(const Point& point) std::cout << point.getX() << ’ ’ << point.getY() << ’\n’; Nous pouvons avoir : auto p = std::make_unique<Point>(3.0, 4.0); La fonction reçoit une référence vers le Point, elle ne reçoit pas la propriété de la ressource, après l’appel, p en reste propriétaire. C’est généralement une excellente manière d’exprimer l’intention : la fonction utilise l’objet, mais ne le possède pas.
Accueil
2026 – C++ Partie III std::unique_ptr en pratique La fonction devient propriétaire Supposons maintenant que la fonction doive prendre possession de l’objet. Nous pouvons écrire : void traiter(std::unique_ptr<Point> p) std::cout << p->getX() << ’\n’; Mais l’appel suivant est impossible : auto p = std::make_unique<Point>(3.0, 4.0); Cela demanderait de copier le unique_ptr. Donc nous devons explicitement transférer sa propriété : traiter(std::move(p)); Après cet appel, le unique_ptr local p est vide car la propriété a été transférée au paramètre de la fonction. Lorsque ce paramètre sera détruit, la ressource sera elle aussi détruite, sauf si la fonction transfère à son tour cette propriété ailleurs. • Passer un unique_ptr par valeur exprime une intention forte : la fonction reçoit la propriété de la ressource. • L’appel nécessite donc généralement un transfert explicite avec std::move. Retourner un unique_ptr depuis une fonction Une fonction peut également créer une ressource et en transférer la propriété à son appelant. std::unique_ptr<Point> creerPoint(double x, double y) return std::make_unique<Point>(x, y); Nous pouvons ensuite écrire : auto p = creerPoint(3.0, 4.0); La fonction crée le Point et retourne un unique_ptr qui en possède la propriété. Cette propriété est alors transférée au unique_ptr p de l’appelant. Il n’est pas nécessaire d’écrire : std::make_unique<Point>(x, y) L’écriture simple : return std::make_unique<Point>(x, y); est correcte et préférable.
Accueil
2026 – C++ Partie III std::unique_ptr en pratique Retourner une variable locale Nous pouvons également rencontrer : std::unique_ptr<Point> creerPoint(double x, double y) auto p = std::make_unique<Point>(x, y); Cette écriture est également valide. Le langage permet ici le retour de l’objet sans exiger une copie interdite du unique_ptr. Selon le cas, le compilateur peut notamment éliminer la construction intermédiaire ou utiliser les règles de déplacement applicables au retour d’une variable locale. Il n’est généralement pas nécessaire d’écrire : return std::move(p); et cette écriture peut même empêcher certaines optimisations de copie. doit donc être préférée ici. Un unique_ptr peut fournir le pointeur brut vers l’objet qu’il possède. Pour cela, il dispose de : auto p = std::make_unique<int>(42); int* brut = p.get(); Mais il faut être extrêmement précis : brut ne devient pas propriétaire de la ressource. Le propriétaire reste p. Il ne faut donc surtout pas écrire : La ressource sera libérée automatiquement par p.
Accueil
2026 – C++ Partie III std::unique_ptr en pratique Pourquoi utiliser get() ? get() est principalement utile lorsqu’une fonction ou une bibliothèque demande un pointeur brut sans prendre la propriété de l’objet. void ancienneFonction(Point* p); Nous pouvons appeler : auto p = std::make_unique<Point>(3.0, 4.0); ancienneFonction(p.get()); Le unique_ptr reste propriétaire de l’objet et la fonction reçoit seulement son adresse. • get() donne accès au pointeur brut contenu dans un • unique_ptr, mais ne transfère aucune propriété. Le pointeur retourné par get() ne doit donc pas être libéré avec delete. Un unique_ptr peut abandonner la ressource qu’il possède en la détruisant. Nous pouvons écrire : auto p = std::make_unique<int>(42); l’objet dynamique possédé est détruit et unique_ptr devient ensuite vide Nous pouvons également lui confier une nouvelle ressource, bien que dans le code moderne on préfère généralement construire directement la nouvelle propriété de manière claire. L’intérêt essentiel de reset() est de permettre la destruction anticipée de la ressource sans détruire le unique_ptr lui-même. La fonction release() Il existe une autre opération dont le comportement est très différent : auto p = std::make_unique<int>(42); int* brut = p.release();
Accueil
2026 – C++ Partie III std::unique_ptr en pratique Après cette opération, p est vide, mais contrairement à reset(), l’objet dynamique n’a pas été détruit. Nous avons maintenant : Le unique_ptr a abandonné la propriété de la ressource et retourné son adresse et la gestion automatique de cette ressource a cessé. Dans cet exemple particulier, si aucun autre mécanisme ne reprend la propriété, il faudrait finalement écrire pour éviter une fuite de mémoire. get() et release() ne font pas la même chose La distinction est fondamentale, avec int* brut = p.get(); p reste propriétaire et avec int* brut = p.release(); p abandonne la propriété. Nous pouvons résumer : conservée par unique_ptr • release() doit être utilisé avec prudence. • Il retire la ressource du contrôle du unique_ptr sans la détruire. • À partir de cet instant, un autre mécanisme doit reprendre la responsabilité de cette ressource, faute de quoi une fuite est possible. unique_ptr et tableaux dynamiques Un unique_ptr peut également posséder un tableau dynamique. Nous pouvons écrire : auto p = std::make_unique<double[]>(1000); Le type déduit est : std::unique_ptr<double[]> Nous pouvons accéder aux éléments avec l’opérateur [] :
Accueil
2026 – C++ Partie III std::cout << p[0] << ’\n’; std::unique_ptr en pratique Lorsque p est détruit, le tableau est automatiquement libéré correctement et il n’est plus nécessaire d’écrire : La distinction entre destruction d’un objet unique et destruction d’un tableau est gérée par le type du Mais faut-il utiliser unique_ptr<T[]> ? La possibilité existe et peut être utile dans certaines situations. Cependant, pour représenter une collection dynamique d’objets, le C++ moderne fournit généralement un outil plus approprié qui est : Un vector connaît notamment sa taille et fournit de nombreuses opérations destinées à la gestion d’une auto p = std::make_unique<double[]>(1000); ne doit pas être considéré comme le remplacement systématique de : double* p = new double[1000]; Dans de nombreux cas, nous préférerons : std::vector<double> valeurs(1000); Nous étudierons ce conteneur dans un chapitre ultérieur. Faut-il toujours utiliser un unique_ptr ? L’étude des pointeurs intelligents pourrait donner l’impression qu’en C++ moderne il faut créer les objets avec make_unique mais ce serait une conclusion incorrecte. Si la durée de vie de cet objet correspond naturellement à la portée dans laquelle il est utilisé, cette écriture est simple et efficace. Il n’y a aucune raison de remplacer systématiquement cet objet par : auto p = std::make_unique<Point>(3.0, 4.0); Cette deuxième écriture introduit une allocation dynamique et une indirection supplémentaires. Un unique_ptr est utile lorsqu’une allocation dynamique et une notion de propriété sont réellement nécessaires.
Accueil
2026 – C++ Partie III std::unique_ptr en pratique • Le C++ moderne ne recommande pas de remplacer tous les objets par des pointeurs intelligents. • Lorsque l’allocation dynamique n’est pas nécessaire, un objet automatique ordinaire est généralement préférable : • Lorsqu’une ressource dynamique doit posséder un propriétaire unique, std::unique_ptr constitue alors l’outil naturel. unique_ptr et pointeur brut : deux rôles différents Nous pouvons maintenant distinguer deux notions qui étaient souvent confondues dans le C++ traditionnel. qui représente essentiellement une adresse. À lui seul, son type ne précise pas clairement si le pointeur est propriétaire de l’objet désigné. std::unique_ptr<Point> qui exprime au contraire explicitement une notion de propriété. Cet objet possède exclusivement la ressource désignée et devra assurer sa destruction. Cette information fait donc partie du type lui-même. C’est l’une des évolutions importantes de la gestion moderne des ressources : la notion de propriété devient beaucoup plus visible dans le code. std::unique_ptr représente la propriété exclusive d’une ressource et on le construit généralement avec : auto p = std::make_unique<Type>(arguments); Il ne peut pas être copié : mais sa propriété peut être transférée auto p2 = std::move(p1); Un unique_ptr peut être vide et peut être testé simplement avec : Lorsqu’une fonction doit seulement utiliser l’objet sans en devenir propriétaire, on peut généralement lui transmettre directement une référence vers l’objet
Accueil
2026 – C++ Partie III std::unique_ptr en pratique Mais lorsqu’une fonction doit devenir propriétaire, le unique_ptr peut lui être transmis par valeur avec un traiter(std::move(p)); Les principales opérations particulières sont : obtenir l’adresse sans transférer la propriété, détruire la ressource possédée, abandonner la propriété sans détruire la ressource. Enfin, unique_ptr ne doit pas être utilisé lorsqu’aucune allocation dynamique n’est nécessaire. Le principe général est donc : Utiliser un objet automatique lorsque cela suffit ; utiliser unique_ptr lorsqu’une ressource dynamique doit avoir un propriétaire unique.
Accueil
P4-Chap-04 std::unique_ptr en pratique
Accueil
Termes à ajouter au glossaire
Contenu

Inscription

×
Cancel