Pour autant que je sache, C++ 14 a introduit std::make_unique
parce que l'ordre d'évaluation des paramètres n'étant pas spécifié, il s'agissait d'un problème de sécurité:
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 construction std::unique_ptr
, la mémoire est alors perdue.)
L'appel de std::make_unique
était un moyen de contraindre l'ordre des appels, rendant ainsi les choses sûres:
f(std::make_unique<MyClass>(param), g()); // Syntax B
Depuis lors, C++ 17 a clarifié l'ordre d'évaluation, rendant Syntax A safe aussi, voici donc ma question: existe-t-il encore une raison d'utiliser std::make_unique
sur le constructeur de std::unique_ptr
en C +? +17? Pouvez-vous donner quelques exemples?
Pour l’instant, la seule raison que je puisse imaginer est que cela permet de taper MyClass
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, en particulier lorsque std::make_unique
ne permet pas de spécifier un suppresseur alors que le constructeur de std::unique_ptr
le fait.
Et juste pour être clair, je ne préconise pas de retirer std::make_unique
de la bibliothèque standard (le conserver est logique du moins pour la compatibilité ascendante), mais je me demande plutôt s'il existe encore des situations dans lesquelles elle est fortement préférée. à std::unique_ptr
Vous avez raison, la raison principale a été supprimée. Il y a toujours les directives n'utilisez pas de nouvelles et qu'il s'agit de raisons moins dactylographiques (il n'est pas nécessaire de répéter le type ou d'utiliser le mot new
). Certes, ces arguments ne sont pas forts, mais j'aime vraiment ne pas voir new
dans mon code.
Aussi, n'oubliez pas de cohérence. Vous devez absolument utiliser make_shared
pour que make_unique
soit naturel et corresponde au motif. Il est alors trivial de changer std::make_unique<MyClass>(param)
en std::make_shared<MyClass>(param)
(ou l'inverse) où la syntaxe A nécessite beaucoup plus de réécriture.
make_unique
distingue T
de T[]
et T[N]
, unique_ptr(new ...)
ne le fait pas.
Vous pouvez facilement obtenir un comportement indéfini en passant un pointeur qui était new[]
ed à un unique_ptr<T>
, ou en passant un pointeur qui était new
ed à un unique_ptr<T[]>
.
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
, new
et les accolades. Cela ne coûte qu'un caractère de plus dans make en comparaison avec ptr.
Chaque utilisation de new
doit faire l'objet d'un audit minutieux pour en assurer l'exactitude à vie; est-il supprimé? Juste une fois?
Chaque utilisation de make_unique
ne correspond pas à ces caractéristiques supplémentaires; tant que la durée de vie de l'objet propriétaire est "correcte", le pointeur unique est récursivement "correct".
Maintenant, il est vrai que unique_ptr<Foo>(new Foo())
est identique à tous les égards1 à make_unique<Foo>()
; cela nécessite simplement un simple "grep votre code source pour toutes les utilisations de new
pour les auditer".
1 en fait un mensonge dans le cas général. Le transfert parfait n'est pas parfait, {}
, init par défaut, les tableaux sont tous des exceptions.
Depuis lors, C++ 17 a clarifié l'ordre d'évaluation, rendant ainsi Syntax A safe aussi.
Ce n'est vraiment pas suffisant. S'appuyer sur une clause technique récemment introduite en tant que garantie de sécurité n'est pas une pratique très robuste:
new
bruts ailleurs, par exemple. en copiant-collant votre exemple.new
ailleurs (ok, d'accord, peu de chance que cela se produise).En règle générale, il est judicieux que votre code soit approprié/robuste/clairement valide sans recourir à la mise en langage, à la recherche de clauses techniques mineures ou obscures dans la norme.
(c’est essentiellement le même argument que j’ai avancé ici à propos de l’ordre de destruction des tuples.)