make_unique et transfert parfait


216

Pourquoi n'y a-t-il pas de std::make_uniquemodèle de fonction dans la bibliothèque C ++ 11 standard? je trouve

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

un peu bavard. Les éléments suivants ne seraient-ils pas beaucoup plus agréables?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

Cela cache newbien et ne mentionne le type qu'une seule fois.

Quoi qu'il en soit, voici ma tentative d'implémentation de make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Il m'a fallu un certain temps pour obtenir les std::forwardéléments à compiler, mais je ne sais pas si c'est correct. C'est ça? Que std::forward<Args>(args)...signifie exactement ? Qu'est-ce que le compilateur en fait?


1
Je suis sûr que nous avons déjà eu cette discussion ... notez également que cela unique_ptrprend un deuxième paramètre de modèle que vous devriez en quelque sorte autoriser - c'est différent de shared_ptr.
Kerrek SB

5
@Kerrek: Je ne pense pas qu'il serait logique de paramétrer make_uniqueavec un suppresseur personnalisé, car il est évident qu'il alloue via plain old newet doit donc utiliser plain old delete:)
fredoverflow

1
@Fred: C'est vrai. Donc, la proposition make_uniqueserait limitée à l' newallocation ... eh bien, c'est bien si vous voulez l'écrire, mais je peux voir pourquoi quelque chose comme ça ne fait pas partie de la norme.
Kerrek SB

4
En fait, j'aime utiliser un make_uniquemodèle car le constructeur de std::unique_ptrest explicite, et donc il est verbeux de revenir unique_ptrd'une fonction. Aussi, je préfère utiliser auto p = make_unique<foo>(bar, baz)que std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.12

Réponses:


157

Herb Sutter, président du comité de normalisation C ++, écrit sur son blog :

Ce que C ++ 11 n'inclut pas make_uniqueest en partie un oubli, et il sera presque certainement ajouté à l'avenir.

Il donne également une implémentation identique à celle donnée par le PO.

Edit: fait std::make_unique maintenant partie de C ++ 14 .


2
Un make_uniquemodèle de fonction ne garantit pas lui-même les appels sécurisés d'exception. Il s'appuie sur la convention, que l'appelant l'utilise. En revanche, la vérification stricte des types statiques (qui est la principale différence entre C ++ et C) repose sur l'idée d' appliquer la sécurité, via les types. Et pour cela, make_uniquepeut simplement être une classe au lieu d'une fonction. Par exemple, consultez mon article de blog de mai 2010. Il est également lié à la discussion sur le blog de Herb.
Bravo et hth. - Alf

1
@ DavidRodríguez-dribeas: lisez le blog de Herb pour comprendre le problème de sécurité d'exception. oubliez "pas conçu pour être dérivé", c'est juste des ordures. au lieu de ma suggestion de créer make_uniqueune classe, je pense maintenant qu'il vaut mieux en faire une fonction qui produit un make_unique_t, la raison en est un problème avec l'analyse la plus perverse :-).
Bravo et hth. - Alf

1
@ Cheersandhth.-Alf: Peut-être que nous avons lu un article différent, parce que celui que je viens de lire indique clairement qu'il make_uniqueoffre la garantie d'exception forte. Ou peut-être que vous mélangez l'outil avec son utilisation, auquel cas aucune fonction n'est sûre d'exception. Considérez void f( int *, int* ){}, offre clairement la no throwgarantie, mais selon votre raisonnement, il n'est pas exceptionnellement sûr, car il peut être utilisé à mauvais escient. Pire, ce void f( int, int ) {}n'est pas une exception non plus!: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas

2
@ Cheersandhth.-Alf: J'ai posé des questions sur les problèmes d'exception mis en make_unique œuvre comme ci-dessus et vous me dirigez vers un article de Sutter, l'article que mon google-fu m'a indiqué aux États qui make_uniqueoffre la garantie d'exception forte , ce qui contredit votre déclaration ci-dessus. Si vous avez un article différent, je suis intéressé à le lire. Donc, ma question d'origine est la suivante: comment make_unique(tel que défini ci-dessus) n'est-il pas une exception sûre? (Sidenote: oui, je pense que cela make_unique améliore la sécurité des exceptions dans les endroits où il peut être appliqué)
David Rodríguez - dribeas

3
... je ne recherche pas non plus une fonction moins sûre à utiliser make_unique. Tout d'abord, je ne vois pas en quoi celui-ci est dangereux, et je ne vois pas comment l'ajout d'un type supplémentaire le rendrait plus sûr . Ce que je sais, c'est que je suis intéressé à comprendre les problèmes que cette mise en œuvre peut avoir - je ne vois aucun - et comment une autre mise en œuvre pourrait les résoudre. Quelles sont les conventions qui make_uniquedépend de? Comment utiliseriez-vous la vérification de type pour renforcer la sécurité? Ce sont les deux questions pour lesquelles j'aimerais une réponse.
David Rodríguez - dribeas

78

Bien, mais Stephan T. Lavavej (mieux connu sous le nom de STL) a une meilleure solution pour make_unique, qui fonctionne correctement pour la version tableau.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

Cela peut être vu sur sa vidéo Core C ++ 6 .

Une version mise à jour de la version STL de make_unique est désormais disponible sous le numéro N3656 . Cette version a été adoptée dans le projet C ++ 14.


make_unique est proposé par Stephen T Lavalej dans la prochaine mise à jour std.
Tominator


pourquoi faire toutes les modifications inutiles xeo, c'était bien comme ça. Ce code était aussi exactement que Stephen T. Lavalej l'a dit, et il travaille pour dinkumware qui gère la bibliothèque std. Vous avez déjà commenté ce mur, vous devriez donc le savoir.
Tominator

3
L'implémentation de make_uniquedevrait aller dans un en-tête. Les en-têtes ne doivent pas importer un espace de noms (voir l'article n ° 59 du livre "C ++ Coding Standards" de Sutter / Alexandrescu). Les changements de Xeo permettent d'éviter d'encourager les mauvaises pratiques.
Bret Kuhns

malheureusement, non pris en charge par VC2010, même VC2012, je pense, qui ne prennent pas en charge le paramètre varidic tempalte
zhaorufei

19

std::make_sharedn'est pas seulement un raccourci pour std::shared_ptr<Type> ptr(new Type(...));. Il fait quelque chose que vous ne pouvez pas faire sans lui.

Afin de faire son travail, std::shared_ptrdoit allouer un bloc de suivi en plus de conserver le stockage pour le pointeur réel. Cependant, comme il std::make_sharedalloue l'objet réel, il est possible qu'il std::make_sharedalloue à la fois l'objet et le bloc de suivi dans le même bloc de mémoire.

Donc, alors std::shared_ptr<Type> ptr = new Type(...);qu'il y aurait deux allocations de mémoire (une pour le new, une dans le std::shared_ptrbloc de suivi), std::make_shared<Type>(...)allouerait une bloc de mémoire.

C'est important pour de nombreux utilisateurs potentiels de std::shared_ptr. La seule chose à std::make_uniquefaire serait d'être un peu plus pratique. Rien de plus que ça.


2
Ce n'est pas obligatoire. Suggéré, mais pas obligatoire.
Puppy

3
Ce n'est pas seulement pour plus de commodité, cela améliorerait également la sécurité des exceptions dans certains cas. Voir la réponse de Kerrek SB pour cela.
Piotr99

19

Bien que rien ne vous empêche d'écrire votre propre assistant, je pense que la principale raison de fournir make_shared<T>dans la bibliothèque est qu'il crée en fait un type de pointeur partagé différent de celui shared_ptr<T>(new T)qui est différemment alloué, et il n'y a aucun moyen d'y parvenir sans le dédié assistant.

Votre make_uniqueemballage, d'autre part, n'est que du sucre syntaxique autour d'une newexpression, alors même s'il peut sembler agréable à l'œil, il n'apporte rien newà la table. Correction: ce n'est pas vrai en fait: avoir un appel de fonction pour encapsuler l' newexpression offre une sécurité d'exception, par exemple dans le cas où vous appelez une fonction void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Avoir deux news bruts non séquencés l'un par rapport à l'autre signifie que si une nouvelle expression échoue avec une exception, l'autre peut entraîner une fuite de ressources. Quant à savoir pourquoi il n'y a pas make_uniquedans la norme: c'était juste oublié. (Cela se produit occasionnellement. Il n'y a pas non plus destd::cbegin dans la norme même s'il devrait y en avoir un.)

Notez également que unique_ptrprend un deuxième paramètre de modèle que vous devriez en quelque sorte autoriser; ceci est différent de shared_ptr, qui utilise l'effacement de type pour stocker des suppresseurs personnalisés sans les intégrer au type.


1
@FredOverflow: le pointeur partagé est une classe relativement compliquée; en interne, il conserve un bloc de contrôle de référence polymorphe, mais il existe plusieurs types de blocs de contrôle. shared_ptr<T>(new T)utilise l'un d'eux, make_shared<T>()utilise un autre. Permettre que c'est une bonne chose, et la version partagée est en quelque sorte le pointeur partagé le plus léger que vous puissiez obtenir.
Kerrek SB

15
@FredOverflow: shared_ptralloue un bloc de mémoire dynamique pour maintenir le décompte et l'action "broyeur" lorsque vous créez un shared_ptr. Si vous passez le pointeur explicitement, il doit créer un "nouveau" bloc, si vous l'utilisez, vous make_sharedpouvez regrouper votre objet et les données satellite dans un seul bloc de mémoire (un new), ce qui entraîne une allocation / désallocation plus rapide, moins de fragmentation et (normalement ) un meilleur comportement du cache.
Matthieu M.

2
Je pense que j'ai besoin de re-regarder shared_ptr et ses amis ...
fredoverflow

4
-1 "Votre wrapper make_unique, d'autre part, n'est que du sucre syntaxique autour d'une nouvelle expression, donc même s'il peut sembler agréable à l'œil, il n'apporte rien de nouveau à la table." est faux. Il apporte la possibilité d'invocation de fonctions d'exception. Elle n'apporte cependant pas de garantie; pour cela, il devrait être de classe afin que les arguments formels puissent être déclarés de cette classe (Herb l'a décrit comme la différence entre opt-in et opt-out).
Bravo et hth. - Alf

1
@ Cheersandhth.-Alf: Oui, c'est vrai. Je l'ai depuis réalisé. Je vais modifier la réponse.
Kerrek SB

13

En C ++ 11 ... est également utilisé (dans le code modèle) pour "l'expansion du pack".

L'exigence est que vous l'utilisiez comme suffixe d'une expression contenant un pack de paramètres non développé, et il appliquera simplement l'expression à chacun des éléments du pack.

Par exemple, en s'appuyant sur votre exemple:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

Ce dernier étant incorrect je pense.

De plus, un pack d'arguments ne peut pas être passé à une fonction non développée. Je ne suis pas sûr d'un pack de paramètres de modèle.


1
Ravi de voir cela expliqué. Pourquoi ne pas inclure également le paramètre de modèle variadic?
Kerrek SB

@Kerrek: parce que je ne suis pas sûr de sa syntaxe étrange, et je ne sais pas si beaucoup de gens ont joué avec. Je vais donc garder ce que je sais. Cela peut justifier une entrée FAQ c ++, si quelqu'un était suffisamment motivé et bien informé, car la syntaxe variadique est assez complète.
Matthieu M.

La syntaxe est std::forward<Args>(args)..., qui se développe en forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB

1
: Je pense forwardqu'en fait, il faut toujours un paramètre de modèle, non?
Kerrek SB

1
@Howard: c'est vrai! Forcer l'instanciation déclenche l'avertissement ( ideone.com/GDNHb )
Matthieu M.

5

Inspiré par l'implémentation de Stephan T. Lavavej, j'ai pensé qu'il serait bien d'avoir une make_unique qui supporte les extensions de tableau, c'est sur github et j'aimerais avoir des commentaires à ce sujet. Cela vous permet de faire ceci:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.