Por lo que tengo entendido, C ++ 14 se introdujo std::make_unique
porque, como resultado de que no se especificaba el orden de evaluación de parámetros, esto no era seguro:
f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A
(Explicación: si la evaluación primero asigna la memoria para el puntero sin procesar, luego llama g()
y se lanza una excepción antes de la std::unique_ptr
construcción, entonces se pierde la memoria).
Llamar std::make_unique
era una forma de restringir el orden de las llamadas, haciendo las cosas seguras:
f(std::make_unique<MyClass>(param), g()); // Syntax B
Desde entonces, C ++ 17 ha aclarado el orden de evaluación, haciendo que la sintaxis A también sea segura, así que aquí está mi pregunta: ¿todavía hay una razón para usar std::make_unique
másstd::unique_ptr
el constructor de en C ++ 17? ¿Puede dar algunos ejemplos?
A partir de ahora, la única razón que puedo imaginar es que permite escribir MyClass
solo una vez (asumiendo que no necesita depender del polimorfismo con std::unique_ptr<Base>(new Derived(param))
). Sin embargo, eso parece una razón bastante débil, especialmente cuando std::make_unique
no permite especificar un eliminador mientras que std::unique_ptr
el constructor de sí lo hace.
Y para ser claros, no estoy abogando a favor de eliminar std::make_unique
de la Biblioteca estándar (mantenerlo tiene sentido al menos para la compatibilidad con versiones anteriores), sino más bien me pregunto si todavía hay situaciones en las que se prefiere fuertementestd::unique_ptr
fuente
std::unique_ptr
? No es un argumento en contramake_unique
std::make_unique
en primer lugar, no creo que sea razón suficiente para agregarlo al STL, especialmente cuando es una sintaxis que es menos expresiva que usar el constructor, no másRespuestas:
Tienes razón en que se eliminó la razón principal. Todavía hay pautas de no usar nuevas y que son menos razones para escribir (no es necesario repetir el tipo o usar la palabra
new
). Es cierto que esos no son argumentos sólidos, pero realmente me gusta no verlosnew
en mi código.Además, no te olvides de la coherencia. Definitivamente deberías usarlo
make_shared
para quemake_unique
sea natural y se ajuste al patrón. Entonces es trivial cambiarstd::make_unique<MyClass>(param)
astd::make_shared<MyClass>(param)
(o al revés) donde la sintaxis A requiere mucha más reescritura.fuente
new
, necesito detenerme y pensar: ¿cuánto tiempo va a vivir este puntero? ¿Lo manejé correctamente? Si hay una excepción, ¿se limpió todo correctamente? Me gustaría no hacerme esas preguntas y perder mi tiempo en eso y si no las usonew
, no tengo que hacer esas preguntas.new
. ¿No sería maravilloso?std::make_shared
- imaginar un caso en el que el objeto asignado es grande y hay una gran cantidad destd::weak_ptr
apuntamiento -s a ella: sería mejor dejar que el objeto se eliminará tan pronto como la última compartida El puntero se destruye y vive con solo una pequeña área compartida.std::make_shared
stackoverflow.com/a/20895705/8414561 donde la memoria que se usó para almacenar el objeto no se puede liberar hasta questd::weak_ptr
desaparezca el último (incluso si todosstd::shared_ptr
-s apuntan a él (y en consecuencia, el objeto en sí) ya ha sido destruido).make_unique
distingueT
deT[]
yT[N]
,unique_ptr(new ...)
no lo hace.Puede obtener fácilmente un comportamiento indefinido pasando un puntero que fue
new[]
ed a aunique_ptr<T>
, o pasando un puntero que fuenew
ed a aunique_ptr<T[]>
.fuente
La razón es tener un código más corto sin duplicados. Comparar
Te ahorras
MyClass
,new
y brackets. Cuesta solo un personaje más en make en comparación con ptr .fuente
MyClass
, pero me preguntaba si había una razón más sólida para usarlo<MyClass>
pieza en la primera variante.std::unique_ptr
que no esté permitida. Tiene que ver con distinguirstd::unique_ptr<T>
ystd::unique_ptr<T[]>
Cada uso de
new
tiene que ser auditado muy cuidadosamente para asegurar que sea correcto de por vida; ¿Se elimina? ¿Sólo una vez?Cada uso de
make_unique
no lo hace para esas características adicionales; siempre que el objeto propietario tenga una duración "correcta", de forma recursiva hace que el puntero único sea "correcto".Ahora bien, es cierto que
unique_ptr<Foo>(new Foo())
es idéntico en todos los aspectos 1 amake_unique<Foo>()
; solo requiere un "grep de su código fuente para todos los usos denew
para auditarlos".En realidad, una mentira en el caso general. El reenvío perfecto no es perfecto,
{}
init predeterminado, las matrices son todas excepciones.fuente
unique_ptr<Foo>(new Foo)
no es del todo idéntico amake_unique<Foo>()
... este último sí.new Foo()
Pero por lo demás, sí.std::unique_ptr
que no esté permitida. Si tiene que ver con distinguirstd::unique_ptr<T>
ystd::unique_ptr<T[]>
Eso no es lo suficientemente bueno. Confiar en una cláusula técnica recientemente introducida como garantía de seguridad no es una práctica muy sólida:
new
en otros lugares, por ejemplo, copiando y pegando su ejemplo.new
otra parte (está bien, concedido, no hay muchas posibilidades de eso).Generalmente, es una buena idea que su código sea apropiado / robusto / claramente válido sin tener que recurrir a la disposición de lenguaje, buscar cláusulas técnicas menores u oscuras en el estándar.
(Este es esencialmente el mismo argumento que hice aquí sobre el orden de destrucción de tuplas).
fuente
Considere la función void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}
Suponga que el nuevo A () tiene éxito, pero el nuevo B () arroja una excepción: lo detecta para reanudar la ejecución normal de su programa. Desafortunadamente, el estándar C ++ no requiere que el objeto A sea destruido y su memoria desasignada: la memoria se filtra silenciosamente y no hay forma de limpiarla. Al envolver A y B en std :: make_uniques, está seguro de que la fuga no ocurrirá:
void function (std :: make_unique (), std :: make_unique ()) {...} El punto aquí es que std :: make_unique y std :: make_unique ahora son objetos temporales, y la limpieza de objetos temporales está correctamente especificada en el estándar C ++: sus destructores se activarán y se liberará la memoria. Entonces, si puede, siempre prefiera asignar objetos usando std :: make_unique y std :: make_shared.
fuente