Diferencias entre std :: make_unique y std :: unique_ptr con new

130

¿ std::make_uniqueTiene algún beneficio de eficiencia como std::make_shared?

En comparación con la construcción manual std::unique_ptr:

std::make_unique<int>(1);         // vs
std::unique_ptr<int>(new int(1));
NFRCR
fuente
¿ make_sharedTiene alguna eficiencia sobre solo escribir el código de mano larga?
Ed Heal
9
@EdHeal Puede, porque make_sharedpuede asignar tanto el espacio para el objeto como el espacio para el bloque de control juntos en una sola asignación. El costo de esto es que el objeto no se puede desasignar por separado del bloque de control, por lo que si usa weak_ptrmucho, puede terminar usando más memoria.
bames53
Quizás este sea un buen punto de partida stackoverflow.com/questions/9302296/…
Ed Heal

Respuestas:

140

La motivación detrás make_uniquees principalmente doble:

  • make_uniquees seguro para crear temporarios, mientras que con el uso explícito newdebe recordar la regla sobre no usar temporarios sin nombre.

    foo(make_unique<T>(), make_unique<U>()); // exception safe
    
    foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
  • La adición de make_uniquefinalmente significa que podemos decirle a la gente que 'nunca' use en newlugar de la regla anterior a "nunca" usar, newexcepto cuando realice un unique_ptr".

También hay una tercera razón:

  • make_uniqueno requiere uso de tipo redundante. unique_ptr<T>(new T())->make_unique<T>()

Ninguno de los motivos implica mejorar la eficiencia del tiempo de ejecución de la manera en que lo make_sharedhace (debido a evitar una segunda asignación, a costa de un uso de memoria pico potencialmente mayor).

* Se espera que C ++ 17 incluya un cambio de regla que significa que esto ya no es inseguro. Véanse los documentos del comité C ++ P0400R0 y P0145R3 .

bames53
fuente
Tendría más sentido decirlo std::unique_ptry std::shared_ptres por eso que podemos decirle a la gente que "nunca lo use new".
Timothy Shields
2
@TimothyShields Sí, eso es lo que quiero decir. Es solo que en C ++ 11 tenemos make_sharedy también lo make_uniquees la pieza final que faltaba anteriormente.
bames53
1
¿Alguna forma de mencionar brevemente, o vincular, la razón por la que no se utilizan temporarios sin nombre?
Dan Nissenbaum
14
En realidad, desde stackoverflow.com/a/19472607/368896 , lo tengo ... A partir de esa respuesta, considere la siguiente función llamada f: f(unique_ptr<T>(new T), function_that_can_throw());- citar la respuesta: El compilador se le permite a la llamada (en orden): new T, function_that_can_throw(), unique_ptr<T>(...). Obviamente, si en function_that_can_throwrealidad tira, entonces goteas. make_uniquePreviene este caso. Entonces, mi pregunta está respondida.
Dan Nissenbaum
3
Una razón por la que una vez tuve que usar std :: unique_ptr <T> (new T ()) fue porque el constructor de T era privado. Incluso si la llamada a std :: make_unique estaba en un método de fábrica pública de clase T, no se compiló porque uno de los métodos subyacentes de std :: make_unique no podía acceder al constructor privado. No quería que ese método fuera amigo porque no quería confiar en la implementación de std :: make_unique. Entonces, la única solución fue llamar a new en mi método de fábrica de clase T, y luego envolverlo en std :: unique_ptr <T>.
Patrick
14

std::make_uniquey std::make_sharedestán ahí por dos razones:

  1. Para que no tenga que enumerar explícitamente los argumentos de tipo de plantilla.
  2. Excepcional seguridad adicional sobre el uso std::unique_ptro std::shared_ptrconstructores. (Vea la sección de Notas aquí .)

No se trata realmente de la eficiencia del tiempo de ejecución. Hay un poco sobre el bloque de control y la Tasignación de todos a la vez, pero creo que eso es más una bonificación y menos una motivación para que estas funciones existan.

Timothy Shields
fuente
También están ahí para la seguridad de excepción.
0x499602D2
@ 0x499602D2 Y eso, buena adición. Esta página habla de eso.
Timothy Shields
Para futuros lectores, C ++ 17 no permite el entrelazado de argumentos de función, por lo que el argumento de seguridad de excepción ya no es válido. La asignación de memoria para dos paralelos std::make_sharedasegurará que al menos uno de ellos quede envuelto en el puntero inteligente antes de que ocurra la otra asignación de memoria, por lo tanto, no habrá fugas.
MathBunny
7

Una razón por la que tendría que usar std::unique_ptr(new A())o std::shared_ptr(new A())directamente en lugar de std::make_*()no poder acceder al constructor de la clase Afuera del alcance actual.

Volodymyr Lashko
fuente
0

Considerar llamada de función

void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... }

Supongamos que lo nuevo A()tiene éxito, pero lo 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 se destruya y se desasigne su memoria: la memoria se pierde silenciosamente y no hay forma de limpiarla. Al envolver A y B en std::make_uniquesusted, está seguro de que no ocurrirá la fuga:

void function(std::make_unique<A>(), std::make_unique<B>()) { ... }

El punto aquí es que std::make_unique<A>y std::make_unique<B>ahora son objetos temporales, y la limpieza de los objetos temporales está especificada correctamente 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_uniquey std::make_shared.

Dhanya Gopinath
fuente