Pourquoi `make_unique ` est-il interdit?

Supposons que l’espace de nom std

Le projet N3690 du comité C ++ 14 définit std::make_unique ainsi:

[n3690: 20.9.1.4]: création unique_ptr [unique.ptr.create]

template unique_ptr make_unique(Args&&... args);

1 Remarques: Cette fonction ne doit pas participer à la résolution de surcharge, sauf si T n’est pas un tableau.
2 renvoie: unique_ptr(new T(std::forward(args)...)).

template unique_ptr make_unique(size_t n);

3 Remarques: Cette fonction ne doit pas participer à la résolution de la surcharge sauf si T est un tableau de bornes inconnues.
4 renvoie: unique_ptr(new typename remove_extent::type[n]()).

template unspecified make_unique(Args&&...) = delete;

5 Remarques: Cette fonction ne participera pas à la résolution de la surcharge sauf si T est un tableau de liaisons connues.

Maintenant, cela me semble être à peu près aussi clair que de la boue et je pense que cela nécessite davantage d’exposition. Mais, mis à part ce commentaire éditorial, je crois avoir décodé le sens de chaque variante:

  1. template unique_ptr make_unique(Args&&... args);

    Votre make_unique standard pour les types non-array. Vraisemblablement, la “remarque” indique qu’une certaine forme d’assertion statique ou astuce SFINAE consiste à empêcher l’instanciation du modèle lorsque T est un type de tableau.

    À un niveau élevé, voyez-le comme le pointeur intelligent équivalent à T* ptr = new T(args); .

  2. template unique_ptr make_unique(size_t n);

    Une variante pour les types de tableau. Crée un tableau alloué de manière dynamic de n × Ts et le retourne dans un unique_ptr .

    À un niveau élevé, voyez-le comme le pointeur intelligent équivalent à T* ptr = new T[n]; .

  3. template unspecified make_unique(Args&&...)

    Non autorisé. ” non spécifié ” serait probablement unique_ptr .

    Serait autrement le pointeur intelligent équivalent à quelque chose comme T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N]; invalide T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N]; .

Tout d’abord, ai-je raison? Et, si oui, que se passe-t-il avec la troisième fonction?

  • S’il est possible d’empêcher les programmeurs d’essayer d’allouer de manière dynamic un tableau tout en fournissant des arguments de constructeur pour chaque élément (de même que new int[5](args) est impossible), cela est déjà couvert par le fait que la première fonction ne peut pas être instanciée pour les types de tableaux, n’est-ce pas?

  • S’il est là pour empêcher l’ajout au langage d’une construction telle que T[N]* ptr = new T[N] (où N est un certain constexpr ), alors, pourquoi? Ne pourrait-il pas exister un unique_ptr qui enveloppe un bloc alloué de manière dynamic de N × T s? Serait-ce une si mauvaise chose, dans la mesure où le comité s’est make_unique d’empêcher sa création à l’aide de make_unique ?

Pourquoi make_unique interdit?

Citant la proposition originale :

T[N]

À partir de N3485, unique_ptr ne fournit pas de spécialisation partielle pour T[N] . Cependant, les utilisateurs seront fortement tentés d’écrire make_unique() . Ceci est un scénario sans victoire. Renvoyer unique_ptr sélectionnerait le modèle principal d’objects uniques, ce qui est bizarre. Le unique_ptr serait une exception à la règle par ailleurs ironique qui make_unique() renvoie unique_ptr . Par conséquent, cette proposition rend T[N] mal formé ici, ce qui permet aux implémentations d’émettre des messages static_assert utiles.

L’auteur de la proposition, Stephan T. Lavavej, illustre cette situation dans cette vidéo sur Core C ++ (avec la permission de chris ), à partir de la minute 1:01:10 (plus ou moins).

Pour moi, la troisième surcharge semble superflue car cela ne change rien au fait que les autres surcharges ne correspondent pas à T[N] et que cela ne semble pas aider à générer de meilleurs messages d’erreur. Considérez l’implémentation suivante:

 template< typename T, typename... Args > typename enable_if< !is_array< T >::value, unique_ptr< T > >::type make_unique( Args&&... args ) { return unique_ptr< T >( new T( forward< Args >( args )... ) ); } template< typename T > typename enable_if< is_array< T >::value && extent< T >::value == 0, unique_ptr< T > >::type make_unique( const size_t n ) { using U = typename remove_extent< T >::type; return unique_ptr< T >( new U[ n ]() ); } 

Lorsque vous essayez d’appeler std::make_unique(1) , le message d’erreur indique que les deux candidats sont désactivés par enable_if . Si vous ajoutez la troisième surcharge supprimée, le message d’erreur répertorie trois candidats. Aussi, puisqu’il est spécifié comme =delete; , vous ne pouvez pas fournir de message d’erreur plus significatif dans le corps de la troisième surcharge, par exemple static_assert(sizeof(T)==0,"array of known bound not allowed for std::make_shared"); .

Voici l’ exemple en direct au cas où vous voudriez jouer avec.

Le fait que la troisième surcharge se soit retrouvée dans N3656 et N3797 est probablement dû à l’historique de la façon dont make_unique été développé au fil du temps, mais je suppose que seule la société STL peut répondre à cette question 🙂