web-dev-qa-db-fra.com

Dois-je utiliser shared_ptr ou unique_ptr

J'ai créé des objets en utilisant l'idiome pimpl, mais je ne sais pas s'il faut utiliser std::shared_ptr ou std::unique_ptr .

Je comprends que std::unique_ptr est plus efficace, mais ce n'est pas vraiment un problème pour moi, car ces objets sont relativement lourds de toute façon, donc le coût de std::shared_ptr plus de std::unique_ptr est relativement mineur.

Je vais actuellement avec std::shared_ptr juste à cause de la flexibilité supplémentaire. Par exemple, en utilisant un std::shared_ptr me permet de stocker ces objets dans une table de hachage pour un accès rapide tout en étant en mesure de renvoyer des copies de ces objets aux appelants (car je pense que tous les itérateurs ou références peuvent rapidement devenir invalides).

Cependant, ces objets d'une certaine manière ne sont vraiment pas copiés, car les changements affectent toutes les copies, donc je me demandais peut-être qu'en utilisant std::shared_ptr et autoriser les copies est une sorte d'anti-modèle ou de mauvaise chose.

Est-ce correct?

52
Clinton

J'ai créé des objets en utilisant l'idiome pimpl, mais je ne sais pas s'il faut utiliser shared_ptr ou unique_ptr.

Absolument unique_ptr ou scoped_ptr.

Pimpl n'est pas un modèle, mais un idiome, qui traite de la dépendance à la compilation et de la compatibilité binaire. Elle ne doit pas affecter la sémantique des objets, notamment en ce qui concerne son comportement de copie.

Vous pouvez utiliser le type de pointeur intelligent que vous souhaitez sous le capot, mais ces 2 garantissent que vous ne partagerez pas accidentellement l'implémentation entre deux objets distincts, car ils nécessitent une décision consciente sur l'implémentation du constructeur de copie et de l'opérateur d'affectation.

Cependant, ces objets d'une certaine manière ne sont vraiment pas copiés, car les changements affectent toutes les copies, donc je me demandais peut-être qu'en utilisant shared_ptr et autoriser les copies est une sorte d'anti-modèle ou de mauvaise chose.

Ce n'est pas un anti-pattern, en fait, c'est un pattern: l'aliasing. Vous l'utilisez déjà, en C++, avec des pointeurs et des références nus. shared_ptr offre une mesure supplémentaire de "sécurité" pour éviter les références mortes, au prix d'une complexité supplémentaire et de nouveaux problèmes (attention aux cycles qui créent des fuites de mémoire).


Sans rapport avec Pimpl

Je comprends unique_ptr est plus efficace, mais ce n'est pas vraiment un problème pour moi, car ces objets sont relativement lourds de toute façon, donc le coût de shared_ptr plus de unique_ptr est relativement mineur.

Si vous pouvez factoriser un état, vous voudrez peut-être jeter un œil au modèle Flyweight .

36
Matthieu M.

Si tu utilises shared_ptr, ce n'est pas vraiment l'idiome pimpl classique (à moins que vous ne preniez des mesures supplémentaires). Mais la vraie question est de savoir pourquoi vous souhaitez utiliser un pointeur intelligent pour commencer; il est très clair où le delete doit se produire, et il n'y a aucun problème de sécurité d'exception ou autre à se soucier. Tout au plus, un pointeur intelligent vous fera économiser une ou deux lignes de code. Et le seul qui a la sémantique correcte est boost::scoped_ptr, et je ne pense pas que cela fonctionne dans ce cas. (IIRC, il faut un type complet pour être instancié, mais je peux me tromper.)

Un aspect important de l'idiome pimpl est que son utilisation doit être transparente pour le client; la classe doit se comporter exactement comme si elle était implémentée de façon classique. Cela signifie soit inhiber la copie et l'affectation, soit implémenter une copie approfondie, à moins que la classe ne soit immuable (pas de fonctions membres non const). Aucun des pointeurs intelligents habituels n'implémente la copie profonde; vous pouvez en implémenter un, bien sûr, mais cela nécessiterait probablement un type complet chaque fois que la copie se produit, ce qui signifie que vous devrez toujours fournir un constructeur de copie défini par l'utilisateur et un opérateur d'affectation (car ils ne peuvent pas être en ligne). Compte tenu de cela, cela ne vaut probablement pas la peine d'utiliser le pointeur intelligent.

Une exception est si les objets sont immuables. Dans ce cas, peu importe que la copie soit profonde ou non, et shared_ptr gère complètement la situation.

10
James Kanze

Lorsque vous utilisez un shared_ptr (par exemple dans un conteneur, puis recherchez-le et renvoyez-le par valeur), vous ne générez pas une copie de l'objet vers lequel il pointe, simplement une copie du pointeur avec une référence compter.

Cela signifie que si vous modifiez l'objet sous-jacent à partir de plusieurs points, vous affectez les modifications sur même instance. C'est exactement pour cela qu'il est conçu, donc pas un peu anti-pattern!

Lors du passage d'un shared_ptr (comme le disent les commentaires), il est préférable de passer par référence const et de copier (là en incrémentant le nombre de références) si nécessaire. Quant au retour, au cas par cas.

5
Nim

Oui, veuillez les utiliser. Autrement dit, le shared_ptr est une implémentation du pointeur intelligent. unique_ptr est une implémentation du pointeur automatique:

0
Trombe