c++ - make_unique y reenvío perfecto
c++11 variadic-templates (6)
Bien, pero Stephan T. Lavavej (mejor conocido como STL) tiene una mejor solución para 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.
¿Por qué no hay std::make_unique
plantilla de función std::make_unique
en la biblioteca estándar de C ++ 11? Encuentro
std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));
un poco detallado ¿Lo siguiente no sería mucho mejor?
auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);
Esto oculta el new
muy 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 tomó bastante tiempo compilar el std::forward
, pero no estoy seguro de si es correcto. ¿Lo es? ¿Qué significa exactamente std::forward<Args>(args)...
? ¿Qué hace el compilador de eso?
En C ++ 11 ...
se usa (en el código de la plantilla) para "expansión de paquete".
El requisito es que lo uses como un sufijo de una expresión que contenga un paquete de parámetros no expandido, y simplemente aplicará la expresión a cada uno de los elementos del paquete.
Por ejemplo, construyendo sobre tu 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)
Lo último es incorrecto, creo.
Además, el paquete de argumentos no se puede pasar a una función que no esté expandida. No estoy seguro acerca de un paquete de parámetros de plantilla.
Herb Sutter, presidente del comité de estandarización de C ++, escribe en su blog :
El hecho de que C ++ 11 no incluya
make_unique
es en parte un descuido, y es casi seguro que se agregará en el futuro.
También da una implementación que es idéntica a la dada por el OP.
Edit: std::make_unique
ahora es parte de C++14 .
Inspirado por la implementación de Stephan T. Lavavej, pensé que sería bueno tener un make_unique que apoyara extensiones de array, 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);
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 de puntero compartido interno diferente al de shared_ptr<T>(new T)
, que es diferente asignado, y no hay manera de lograr esto sin el ayudante dedicado.
Corrección: esto no es en realidad cierto: tener una llamada a la función para envolver la make_unique
otro lado, su envoltorio make_unique
es mero azúcar sintáctico en torno a una new
expresión, por lo que, aunque parezca agradable a la vista, no trae nada new
a la mesa.new
expresión proporciona una 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 new
s sin procesar que no tienen secuencia entre sí 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_unique
en el estándar: fue olvidado. (Esto sucede ocasionalmente. Tampoco hay un estándar estándar std::cbegin
en el estándar, aunque debería haber uno).
También tenga en cuenta que unique_ptr
toma un segundo parámetro de plantilla que debería permitir de alguna manera; esto es diferente de shared_ptr
, que utiliza el borrado de tipo para almacenar eliminaciones personalizadas sin que sean parte del tipo.
std::make_shared
no es solo una abreviatura de std::shared_ptr<Type> ptr(new Type(...));
. Hace algo que no puedes hacer sin él.
Para realizar su trabajo, std::shared_ptr
debe asignar un bloque de seguimiento además de mantener el almacenamiento para el puntero real. Sin embargo, dado que std::make_shared
asigna el objeto real, es posible que std::make_shared
asigne tanto el objeto como el bloque de seguimiento en el mismo bloque de memoria.
Entonces, mientras que std::shared_ptr<Type> ptr = new Type(...);
serían dos asignaciones de memoria (una para el new
, una en el bloque de seguimiento std::shared_ptr
), std::make_shared<Type>(...)
asignaría un bloque de memoria.
Eso es importante para muchos usuarios potenciales de std::shared_ptr
. Lo único que haría std::make_unique
es ser un poco más conveniente. Nada más que eso.