web-dev-qa-db-fra.com

make_unique et transmission parfaite

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

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

un peu prolixe. Ce qui suit ne serait-il pas beaucoup plus agréable?

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

Ceci cache joliment le new 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 bon bout de temps pour compiler le fichier std::forward, Mais je ne sais pas si c'est correct. Est-ce Que signifie exactement std::forward<Args>(args)...? Qu'est-ce que le compilateur en fait?

214
fredoverflow

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

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

Il donne également une implémentation identique à celle donnée par l'OP.

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

155
Johan Råde

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)...);
}

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

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

76
tominator

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

En revanche, votre wrapper make_unique N’est que du sucre syntaxique autour d’une expression new. Ainsi, même s’il peut paraître agréable à regarder, il n’apporte rien new à la table. Correction: ce n'est pas vrai: avoir un appel de fonction pour envelopper l'expression new fournit 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 brutes non ordonnées l'une par rapport à l'autre signifie que si une nouvelle expression échoue avec une exception, l'autre risque de perdre des ressources. Quant à savoir pourquoi il n’ya pas de make_unique Dans la norme: c’était juste oublié. (Cela arrive occasionnellement. Il n'y a pas non plus de std::cbegin Global dans la norme, même s'il devrait en exister un.)

Notez également que unique_ptr Prend un deuxième paramètre de modèle que vous devriez en quelque sorte permettre; ceci diffère de shared_ptr, qui utilise l'effacement de type dans enregistre les deleters personnalisés sans les inclure dans le type.

19
Kerrek SB

std::make_shared N'est pas simplement un raccourci pour std::shared_ptr<Type> ptr(new Type(...));. Il fait quelque chose que vous ne pouvez pas vous en passer.

Pour faire son travail, std::shared_ptr Doit allouer un bloc de suivi en plus de conserver la mémoire pour le pointeur actuel. Cependant, comme std::make_shared Alloue l'objet réel, il est possible que std::make_shared Alloue à la fois l'objet et le bloc de suivi dans le même bloc de mémoire.

Ainsi, alors que std::shared_ptr<Type> ptr = new Type(...); serait deux affectations de mémoire (une pour le new, une dans le bloc de suivi std::shared_ptr), std::make_shared<Type>(...) allouerait n bloc de mémoire.

C'est important pour de nombreux utilisateurs potentiels de std::shared_ptr. La seule chose que ferait un std::make_unique Est un peu plus pratique. Rien de plus que ça.

19
Nicol Bolas

En C++ 11 ... est également utilisé (dans le code du 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 expansé et qu’elle appliquera simplement l’expression à chacun des éléments du pack.

Par exemple, en vous basant 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.

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

13
Matthieu M.

Inspiré par l’implémentation de Stephan T. Lavavej, j’ai pensé qu’il pourrait être agréable d’avoir un make_unique qui prenne en charge les étendues de tableaux, c’est sur github et j'aimerais recevoir 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); 
5
Nathan Binkert