make_unique y reenvío perfecto


216

¿Por qué no hay std::make_uniqueuna plantilla de función en la biblioteca estándar de C ++ 11? Encuentro

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

Un poco detallado. ¿No sería mucho mejor lo siguiente?

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

Esto oculta newmuy bien y solo menciona el tipo una vez.

De todos modos, aquí está mi intento de implementar 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)...));
}

Me llevó bastante tiempo std::forwardcompilar las cosas, pero no estoy seguro de si es correcto. ¿Lo es? ¿Qué std::forward<Args>(args)...significa exactamente ? ¿Qué hace el compilador de eso?


1
Estoy bastante seguro de que hemos tenido esta discusión antes ... también tenga en cuenta que unique_ptrtoma un segundo parámetro de plantilla que de alguna manera debería permitir, que es diferente shared_ptr.
Kerrek SB

55
@Kerrek: no creo que tendría sentido para parametrizar make_uniquecon un Deleter costumbre, porque obviamente se asigna a través de llanura de edad newy por lo tanto debe utilizar el viejo y simple delete:)
fredoverflow

1
@ Fred: Eso es cierto. Entonces, la propuesta make_uniquese limitaría a la newasignación ... bueno, está bien si quieres escribirla, pero puedo ver por qué algo así no es parte del estándar.
Kerrek SB

44
En realidad, me gusta usar una make_uniqueplantilla ya que el constructor de std::unique_ptres explícito y, por lo tanto, es detallado regresar unique_ptrde una función. Además, prefiero usar auto p = make_unique<foo>(bar, baz)que std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.

Respuestas:


157

Herb Sutter, presidente del comité de estandarización de C ++, escribe en su blog :

Que C ++ 11 no incluya make_uniquees en parte un descuido, y casi seguramente se agregará en el futuro.

También ofrece una implementación que es idéntica a la dada por el OP.

Editar: std::make_unique ahora es parte de C ++ 14 .


2
Una make_uniqueplantilla de función en sí misma no garantiza llamadas seguras de excepción. Se basa en la convención, que la persona que llama lo usa. En contraste, la estricta verificación de tipo estático (que es la diferencia principal entre C ++ y C) se basa en la idea de hacer cumplir la seguridad, a través de los tipos. Y para eso, make_uniquesimplemente puede ser una clase en lugar de una función. Por ejemplo, vea el artículo de mi blog de mayo de 2010. También está vinculado a la discusión en el blog de Herb.
Saludos y hth. - Alf

1
@ DavidRodríguez-dribeas: lea el blog de Herb para comprender el problema de seguridad de excepción. olvídate de "no diseñado para ser derivado", eso es solo basura. en lugar de mi sugerencia de hacer make_uniqueuna clase, ahora creo que es mejor convertirla en una función que produzca un make_unique_t, la razón de esto es un problema con el análisis más perverso :-).
Saludos y hth. - Alf

1
@ Cheersandhth.-Alf: Quizás hayamos leído un artículo diferente, porque el que acabo de leer dice claramente que make_uniqueofrece la garantía de una fuerte excepción. O tal vez está mezclando la herramienta con su uso, en cuyo caso ninguna función es segura. Considere void f( int *, int* ){}, ofrece claramente la no throwgarantía, pero según su línea de razonamiento, no es una excepción segura, ya que puede ser mal utilizada. ¡Peor, void f( int, int ) {}tampoco es una excepción segura !: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas

2
@ Cheersandhth.-Alf: Le pregunté acerca de los problemas de excepción en la make_unique práctica como arriba y me apunto a un artículo de Sutter, el artículo que mi google-fu me señaló a los estados que make_uniqueofrece la garantía fuerte excepción , lo que contradice su declaración anterior. Si tienes un artículo diferente, estoy interesado en leerlo. Entonces, mi pregunta original es ¿Cómo es make_unique(como se definió anteriormente) una excepción segura? (Nota: sí, creo que eso make_unique mejora la seguridad de las excepciones en los lugares donde se puede aplicar)
David Rodríguez - dribeas

3
... tampoco busco una función menos segura de usar make_unique. Primero, no veo cómo este es inseguro, y no veo cómo agregar un tipo adicional lo haría más seguro . Lo que sí sé es que estoy interesado en comprender los problemas que puede tener esta implementación, no puedo ver ninguno, y cómo una implementación alternativa los resolvería. ¿Cuáles son las convenciones que make_uniquedepende de? ¿Cómo usaría la verificación de tipo para hacer cumplir la seguridad? Esas son las dos preguntas para las que me encantaría recibir una respuesta.
David Rodríguez - dribeas

78

Agradable, pero Stephan T. Lavavej (mejor conocido como STL) tiene una mejor solución make_unique, que funciona correctamente para la versión de matriz.

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

Esto se puede ver en su video Core C ++ 6 .

Una versión actualizada de la versión de STL de make_unique ahora está disponible como N3656 . Esta versión fue adoptada en el borrador de C ++ 14.


Stephen T Lavalej propuso make_unique en la próxima actualización estándar.
Tominator

Aquí hay un me gusta para él hablando de agregarlo. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
tominator

por qué hacer todas las ediciones innecesarias xeo, estaba bien como estaba. Ese código era exactamente como lo expresó Stephen T. Lavalej, y trabaja para dinkumware que mantiene la biblioteca estándar. Ya has comentado en ese muro, así que debes saberlo.
Tominator

3
La implementación de make_uniquedebe ir en un encabezado. Los encabezados no deben importar un espacio de nombres (vea el Artículo # 59 en el libro "Normas de codificación C ++" de Sutter / Alexandrescu). Los cambios de Xeo ayudan a evitar alentar malas prácticas.
Bret Kuhns

desafortunadamente, no es compatible con VC2010, incluso VC2012, creo, que ambos no admiten el parámetro varimpic tempalte
zhaorufei

19

std::make_sharedno es solo taquigrafía para std::shared_ptr<Type> ptr(new Type(...));. Hace algo que no puedes hacer sin él.

Para hacer su trabajo, std::shared_ptrdebe asignar un bloque de seguimiento además de mantener el almacenamiento para el puntero real. Sin embargo, debido a que std::make_sharedasigna el objeto real, es posible que std::make_sharedasigne tanto el objeto como el bloque de seguimiento en el mismo bloque de memoria.

Entonces, si bien std::shared_ptr<Type> ptr = new Type(...);serían dos asignaciones de memoria (una para el new, una en el std::shared_ptrbloque de seguimiento), std::make_shared<Type>(...)asignaría un bloque de memoria.

Eso es importante para muchos usuarios potenciales de std::shared_ptr. Lo único que std::make_uniqueharía es ser un poco más conveniente. Nada más que eso.


2
No es requerido Insinuado, pero no requerido.
Cachorro

3
No es solo por conveniencia, también mejoraría la seguridad de excepción en ciertos casos. Vea la respuesta de Kerrek SB para eso.
Piotr99

19

Si bien nada le impide escribir su propio ayudante, creo que la razón principal para proporcionar make_shared<T>en la biblioteca es que en realidad crea un tipo interno diferente de puntero compartido que shared_ptr<T>(new T), que está asignado de manera diferente, y no hay forma de lograr esto sin el dedicado ayudante.

Su make_uniqueenvoltura, por otro lado, es un mero azúcar sintáctico alrededor de una newexpresión, por lo que, aunque pueda parecer agradable a la vista, no aporta nada newa la mesa. Corrección: de hecho, esto no es cierto: tener una llamada de función para ajustar la newexpresión proporciona seguridad de excepción, por ejemplo, en el caso de que llame a una función void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Tener dos news sin procesar sin secuencia uno con respecto al otro significa que si una nueva expresión falla con una excepción, la otra puede perder recursos. En cuanto a por qué no hay make_uniqueun estándar: simplemente se olvidó. (Esto sucede ocasionalmente. Tampoco hay un std::cbeginestándar global en el estándar aunque debería haber uno).

También tenga en cuenta que unique_ptrtoma un segundo parámetro de plantilla que de alguna manera debería permitir; esto es diferente de shared_ptr, que utiliza el borrado de tipos para almacenar eliminadores personalizados sin hacerlos parte del tipo.


1
@FredOverflow: el puntero compartido es una clase relativamente complicada; internamente mantiene un bloque de control de referencia polimórfico, pero hay varios tipos diferentes de bloques de control. shared_ptr<T>(new T)usa uno de ellos, make_shared<T>()usa uno diferente. Permitir que sea una buena cosa, y la versión make-shared es, en cierto sentido, el puntero compartido más liviano que puedes obtener.
Kerrek SB

15
@FredOverflow: shared_ptrasigna un bloque de memoria dinámica para mantener el conteo y la acción de "eliminación" cuando crea un shared_ptr. Si pasa el puntero explícitamente, necesita crear un "nuevo" bloque, si lo usa make_sharedpuede agrupar su objeto y los datos del satélite en un solo bloque de memoria (uno new) resultando en una asignación / desasignación más rápida, menos fragmentación y (normalmente ) mejor comportamiento de caché.
Matthieu M.

2
Creo que necesito volver a ver shared_ptr y amigos ...
fredoverflow

44
-1 "Por otro lado, su envoltorio make_unique es un mero azúcar sintáctico alrededor de una nueva expresión, por lo que si bien puede parecer agradable a la vista, no aporta nada nuevo a la mesa". Está Mal. Trae la posibilidad de invocación de función segura de excepción. Sin embargo, no trae una garantía; para eso, tendría que ser una clase para que los argumentos formales pudieran declararse de esa clase (Herb describió eso como la diferencia entre aceptar y excluir).
Saludos y hth. - Alf

1
@ Cheersandhth.-Alf: Sí, eso es cierto. Desde entonces me di cuenta de esto. Editaré la respuesta.
Kerrek SB

13

En C ++ 11 también ...se usa (en el código de la plantilla) para la "expansión del paquete".

El requisito es que lo use como sufijo de una expresión que contiene un paquete de parámetros sin expandir, y simplemente aplicará la expresión a cada uno de los elementos del paquete.

Por ejemplo, basándose en su ejemplo:

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)

El último es incorrecto, creo.

Además, el paquete de argumentos no se puede pasar a una función sin expandir. No estoy seguro acerca de un paquete de parámetros de plantilla.


1
Es bueno ver esto explicado. ¿Por qué no incluir también el parámetro de plantilla variable?
Kerrek SB

@Kerrek: porque no estoy tan seguro de su extraña sintaxis y no sé si mucha gente ha jugado con ella. Así que me atendré a lo que sé. Puede garantizar una entrada de preguntas frecuentes de c ++, si alguien estaba motivado y tenía los conocimientos suficientes, ya que la sintaxis variadic es bastante completa.
Matthieu M.

La sintaxis es std::forward<Args>(args)..., que se expande a forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB

1
: Creo que en forwardrealidad siempre requiere un parámetro de plantilla, ¿no?
Kerrek SB

1
@Howard: correcto! Forzar instanciación activa la advertencia ( ideone.com/GDNHb )
Matthieu M.

5

Inspirado por la implementación de Stephan T. Lavavej, pensé que sería bueno tener un make_unique que admitiera extensiones de matriz, está en github y me encantaría recibir comentarios al respecto. Te permite hacer esto:

// 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); 
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.