¿Por qué usar std :: make_unique en C ++ 17?

96

Por lo que tengo entendido, C ++ 14 se introdujo std::make_uniqueporque, 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_ptrconstrucción, entonces se pierde la memoria).

Llamar std::make_uniqueera 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_uniquemá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 MyClasssolo 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_uniqueno permite especificar un eliminador mientras que std::unique_ptrel constructor de sí lo hace.

Y para ser claros, no estoy abogando a favor de eliminar std::make_uniquede 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

Eterno
fuente
4
Sin embargo, esa parece una razón bastante débil -> ¿Por qué es una razón débil? Reduce efectivamente la duplicación de código de tipo. En cuanto al eliminador, ¿con qué frecuencia utiliza un eliminador personalizado cuando lo usa std::unique_ptr? No es un argumento en contramake_unique
llllllllll
2
Digo que es una razón débil porque si no hubiera std::make_uniqueen 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ás
Eterno
1
Si tiene un programa, creado en c ++ 14, usando make_unique, no quiere que la función se elimine de stl. O si quieres que sea compatible con versiones anteriores.
Serge
2
@Serge Ese es un buen punto, pero es un poco más allá del objeto de mi pregunta. Haré una edición para que quede más claro
Eterno
1
@Eternal, deje de referirse a la biblioteca estándar de C ++ como STL, ya que es incorrecta y crea confusión. Ver stackoverflow.com/questions/5205491/…
Marandil

Respuestas:

74

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 verlos newen mi código.

Además, no te olvides de la coherencia. Definitivamente deberías usarlo make_sharedpara que make_uniquesea ​​natural y se ajuste al patrón. Entonces es trivial cambiar std::make_unique<MyClass>(param)a std::make_shared<MyClass>(param)(o al revés) donde la sintaxis A requiere mucha más reescritura.

NathanOliver
fuente
41
@reggaeguitar Si veo un 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 uso new, no tengo que hacer esas preguntas.
NathanOliver
5
Imagina que haces un grep sobre todos los archivos fuente de tu proyecto y no encuentras ninguno new. ¿No sería maravilloso?
Sebastian Mach
5
La principal ventaja de la pauta de "no usar nuevo" es que es simple, por lo que es una pauta fácil de dar a los desarrolladores menos experimentados con los que puede estar trabajando. No me había dado cuenta al principio, pero eso tiene valor en sí mismo
Eterno
@NathanOliver En realidad no absolutamente tiene que estar usando std::make_shared- imaginar un caso en el que el objeto asignado es grande y hay una gran cantidad de std::weak_ptrapuntamiento -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.
Dev Null
1
@NathanOliver, no lo harías. De lo que estoy hablando es de la desventaja de std::make_shared stackoverflow.com/a/20895705/8414561 donde la memoria que se usó para almacenar el objeto no se puede liberar hasta que std::weak_ptrdesaparezca el último (incluso si todos std::shared_ptr-s apuntan a él (y en consecuencia, el objeto en sí) ya ha sido destruido).
Dev Null
50

make_uniquedistingue Tde T[]y T[N],unique_ptr(new ...) no lo hace.

Puede obtener fácilmente un comportamiento indefinido pasando un puntero que fue new[]ed a a unique_ptr<T>, o pasando un puntero que fue newed a a unique_ptr<T[]>.

Caleth
fuente
Es peor: no solo no lo hace, es absolutamente incapaz de hacerlo.
Deduplicador
22

La razón es tener un código más corto sin duplicados. Comparar

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Te ahorras MyClass, newy brackets. Cuesta solo un personaje más en make en comparación con ptr .

SM
fuente
2
Bueno, como dije en la pregunta, puedo ver que se escribe menos con solo una mención de MyClass, pero me preguntaba si había una razón más sólida para usarlo
Eterno
2
En muchos casos, la guía de deducción ayudaría a eliminar la <MyClass>pieza en la primera variante.
2018
9
Ya se dijo en los comentarios para otras respuestas, pero mientras que c ++ 17 introdujo la deducción del tipo de plantilla para los constructores, en el caso de std::unique_ptrque no esté permitida. Tiene que ver con distinguir std::unique_ptr<T>ystd::unique_ptr<T[]>
Eterno
19

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_uniqueno 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 a make_unique<Foo>(); solo requiere un "grep de su código fuente para todos los usos de newpara auditarlos".


En realidad, una mentira en el caso general. El reenvío perfecto no es perfecto, {}init predeterminado, las matrices son todas excepciones.

Yakk - Adam Nevraumont
fuente
Técnicamente unique_ptr<Foo>(new Foo)no es del todo idéntico a make_unique<Foo>()... este último sí. new Foo()Pero por lo demás, sí.
Barry
@barry true, es posible un nuevo operador sobrecargado.
Yakk - Adam Nevraumont
@dedup ¿Qué asquerosa brujería en C ++ 17 es esa?
Yakk - Adam Nevraumont
2
@Deduplicator mientras que c ++ 17 introdujo la deducción del tipo de plantilla para constructores, en el caso de std::unique_ptrque no esté permitida. Si tiene que ver con distinguir std::unique_ptr<T>ystd::unique_ptr<T[]>
Eterno
@ Yakk-AdamNevraumont No me refería a sobrecargar lo nuevo, solo me refería a default-init vs value-init.
Barry
0

Desde entonces, C ++ 17 ha aclarado el orden de evaluación, haciendo que Syntax A sea también segura

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:

  • Alguien podría compilar este código en C ++ 14.
  • Estaría fomentando el uso de raw newen otros lugares, por ejemplo, copiando y pegando su ejemplo.
  • Como sugiere SM, dado que hay duplicación de código, un tipo puede cambiarse sin que se cambie el otro.
  • Algún tipo de refactorización IDE automática podría mover eso a newotra 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).

einpoklum
fuente
-1

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.

Dhanya Gopinath
fuente
4
El autor especificó explícitamente en cuestión que en C ++ 17 ya no se producirán fugas. "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: (...)". No respondiste a su pregunta.
R2RT