Pourquoi utiliser std :: make_unique en C ++ 17?

96

Pour autant que je sache, C ++ 14 a été introduit std::make_uniquecar, en raison de la non-spécification de l'ordre d'évaluation des paramètres, ce n'était pas sûr:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Explication: si l'évaluation alloue d'abord la mémoire pour le pointeur brut, puis appelle g()et une exception est levée avant la std::unique_ptrconstruction, alors la mémoire est perdue.)

L'appel std::make_uniqueétait un moyen de contraindre l'ordre des appels, assurant ainsi la sécurité:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Depuis lors, C ++ 17 a clarifié l'ordre d'évaluation, rendant la syntaxe A sûre également, alors voici ma question: y a-t-il encore une raison d'utiliser le constructeur std::make_uniqueover std::unique_ptren C ++ 17? Peux-tu donner quelques exemples?

À partir de maintenant, la seule raison que je peux imaginer est qu'il ne permet de taper MyClassqu'une seule fois (en supposant que vous n'ayez pas besoin de vous fier au polymorphisme avec std::unique_ptr<Base>(new Derived(param))). Cependant, cela semble être une raison assez faible, surtout quand std::make_uniqueil ne permet pas de spécifier un suppresseur alors que std::unique_ptrle constructeur de le fait.

Et pour être clair, je ne préconise pas la suppression std::make_uniquede la bibliothèque standard (le garder a du sens au moins pour la compatibilité descendante), mais je me demande plutôt s'il existe encore des situations dans lesquelles il est fortement préféré àstd::unique_ptr

Éternel
la source
4
Cependant, cela semble être une raison assez faible -> Pourquoi est-ce une raison faible? Il réduit efficacement la duplication de code de type. En ce qui concerne le suppresseur, à quelle fréquence utilisez-vous un suppresseur personnalisé lorsque vous l'utilisez std::unique_ptr? Ce n'est pas un argument contremake_unique
llllllllll
2
Je dis que c'est une raison faible car s'il n'y avait pas std::make_uniqueen premier lieu, je ne pense pas que ce serait une raison suffisante pour l'ajouter à la STL, surtout quand c'est une syntaxe moins expressive que l'utilisation du constructeur, pas plus
Eternal
1
Si vous avez un programme, créé en c ++ 14, utilisant make_unique, vous ne voulez pas que la fonction soit supprimée de stl. Ou si vous voulez qu'il soit rétrocompatible.
Serge
2
@Serge C'est un bon point, mais c'est un peu en dehors de l'objet de ma question. Je vais faire une modification pour que ce soit plus clair
Eternal
1
@Eternal, arrêtez de faire référence à la bibliothèque standard C ++ comme STL car elle est incorrecte et crée de la confusion. Voir stackoverflow.com/questions/5205491/…
Marandil

Réponses:

74

Vous avez raison de dire que la raison principale a été supprimée. Il y a toujours le ne pas utiliser de nouvelles directives et que ce sont moins de raisons de frappe (pas besoin de répéter le type ou d'utiliser le mot new). Certes, ce ne sont pas des arguments forts mais j'aime vraiment ne pas voir newdans mon code.

N'oubliez pas non plus la cohérence. Vous devez absolument l'utiliser, make_shareddonc l'utilisation make_uniqueest naturelle et correspond au modèle. Il est alors trivial de changement std::make_unique<MyClass>(param)à std::make_shared<MyClass>(param)(ou l'inverse) où la syntaxe A nécessite beaucoup plus d'une ré - écriture.

NathanOliver
la source
41
@reggaeguitar Si je vois un, newje dois m'arrêter et réfléchir: combien de temps ce pointeur va-t-il vivre? Est-ce que je l'ai géré correctement? S'il y a une exception, tout est-il nettoyé correctement? Je voudrais ne pas me poser ces questions et perdre mon temps dessus et si je n'utilise pas new, je n'ai pas à poser ces questions.
NathanOliver
5
Imaginez que vous effectuez un grep sur tous les fichiers source de votre projet et que vous n'en trouvez pas un seul new. Ne serait-ce pas merveilleux?
Sebastian Mach
5
Le principal avantage de la directive «ne pas utiliser de nouveau» est qu'elle est simple, c'est donc une directive facile à donner aux développeurs moins expérimentés avec lesquels vous travaillez. Je n'avais pas réalisé au début, mais cela a une valeur en soi
Eternal
@NathanOliver En fait, vous n'avez absolument pas besoin d'utiliser std::make_shared- imaginez un cas où l'objet alloué est gros et qu'il y a beaucoup de std::weak_ptr-s pointant vers lui: il serait préférable de laisser l'objet supprimé dès le dernier partage Le pointeur est détruit et vit avec juste une petite zone partagée.
Dev Null
1
@NathanOliver vous ne le feriez pas. Ce dont je parle, c'est l'inconvénient de std::make_shared stackoverflow.com/a/20895705/8414561 où la mémoire qui a été utilisée pour stocker l'objet ne peut pas être libérée tant que le dernier std::weak_ptrn'est pas parti (même si tous les std::shared_ptr-s pointent dessus (et par conséquent l'objet lui-même) ont déjà été détruits).
Dev Null
50

make_uniquedistingue Tde T[]et T[N], unique_ptr(new ...)ne fait pas.

Vous pouvez facilement obtenir un comportement indéfini en passant un pointeur qui a été new[]édité sur a unique_ptr<T>, ou en passant un pointeur qui a été newédité sur a unique_ptr<T[]>.

Caleth
la source
C'est pire: non seulement cela ne fonctionne pas, mais il est carrément incapable de le faire.
Deduplicator
22

La raison est d'avoir un code plus court sans doublons. Comparer

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Vous enregistrez MyClass, newet des accolades. Il ne coûte qu'un caractère de plus en make par rapport à ptr .

SM
la source
2
Eh bien, comme je l'ai dit dans la question, je peux voir que c'est moins tapant avec une seule mention MyClass, mais je me demandais s'il y avait une raison plus forte de l'utiliser
Eternal
2
Dans de nombreux cas, le guide de déduction aiderait à éliminer la <MyClass>pièce dans la première variante.
Du
9
Cela a déjà été dit dans les commentaires pour d'autres réponses, mais alors que c ++ 17 a introduit la déduction de type de modèle pour les constructeurs, dans le cas où std::unique_ptrelle n'est pas autorisée. Cela a à voir avec la distinction std::unique_ptr<T>etstd::unique_ptr<T[]>
Eternal
19

Chaque utilisation de newdoit être très soigneusement vérifiée pour vérifier l'exactitude à vie; est-il supprimé? Juste une fois?

Chaque utilisation de make_uniquen'est pas pour ces caractéristiques supplémentaires; tant que l'objet propriétaire a une durée de vie "correcte", il rend récursivement le pointeur unique "correct".

Maintenant, il est vrai que unique_ptr<Foo>(new Foo())c'est identique en tous points 1 à make_unique<Foo>(); il nécessite juste un simple "grep votre code source pour toutes les utilisations de newpour les auditer".


1 mensonge dans le cas général. Le transfert parfait n'est pas parfait, {}par défaut, les tableaux sont toutes des exceptions.

Yakk - Adam Nevraumont
la source
Techniquement unique_ptr<Foo>(new Foo)n'est pas tout à fait identique à make_unique<Foo>()... ce dernier le fait new Foo()Mais sinon, oui.
Barry
@barry true, un opérateur surchargé nouveau est possible.
Yakk - Adam Nevraumont
@dedup qu'est-ce que c'est que la sorcellerie du C ++ 17?
Yakk - Adam Nevraumont
2
@Deduplicator tandis que C ++ 17 a introduit la déduction de type de modèle pour les constructeurs, dans le cas où std::unique_ptrelle n'est pas autorisée. Si a à voir avec la distinction std::unique_ptr<T>etstd::unique_ptr<T[]>
Eternal
@ Yakk-AdamNevraumont Je ne voulais pas dire surcharger nouveau, je voulais simplement dire default-init vs value-init.
Barry
0

Depuis lors, C ++ 17 a clarifié l'ordre d'évaluation, rendant la syntaxe A également sûre

Ce n'est vraiment pas suffisant. S'appuyer sur une clause technique récemment introduite comme garantie de sécurité n'est pas une pratique très robuste:

  • Quelqu'un pourrait compiler ce code en C ++ 14.
  • Vous encourageriez l'utilisation de raw newailleurs, par exemple en copiant-collant votre exemple.
  • Comme SM le suggère, puisqu'il y a duplication de code, un type peut être changé sans que l'autre soit changé.
  • Une sorte de refactoring automatique de l'IDE pourrait déplacer cela newailleurs (d'accord, pas beaucoup de chance).

En règle générale, il est judicieux que votre code soit approprié / robuste / clairement valide sans recourir à des couches de langage, à la recherche de clauses techniques mineures ou obscures dans la norme.

(c'est essentiellement le même argument que j'ai fait ici à propos de l'ordre de destruction des tuple.)

einpoklum
la source
-1

Considérez la fonction void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Supposons que le nouveau A () réussisse, mais que le nouveau B () lève une exception: vous l'attrapez pour reprendre l'exécution normale de votre programme. Malheureusement, le standard C ++ n'exige pas que l'objet A soit détruit et sa mémoire libérée: la mémoire fuit silencieusement et il n'y a aucun moyen de la nettoyer. En enveloppant A et B dans std :: make_uniques, vous êtes sûr que la fuite ne se produira pas:

void function (std :: make_unique (), std :: make_unique ()) {...} Le point ici est que std :: make_unique et std :: make_unique sont maintenant des objets temporaires, et le nettoyage des objets temporaires est correctement spécifié dans le standard C ++: leurs destructeurs seront déclenchés et la mémoire libérée. Donc, si vous le pouvez, préférez toujours allouer des objets en utilisant std :: make_unique et std :: make_shared.

Dhanya Gopinath
la source
4
L'auteur a explicitement spécifié en question qu'en C ++ 17 les fuites ne se produiront plus. "Depuis, C ++ 17 a clarifié l'ordre d'évaluation, rendant la syntaxe A sûre aussi, alors voici ma question: (...)". Vous n'avez pas répondu à sa question.
R2RT