La diferencia es que std::make_shared
realiza una asignación de montón, mientras que llamar al std::shared_ptr
constructor realiza dos.
¿Dónde ocurren las asignaciones del montón?
std::shared_ptr
gestiona dos entidades:
- el bloque de control (almacena metadatos como recuentos de ref, borrador de tipo borrado, etc.)
- el objeto gestionado
std::make_shared
realiza una única contabilidad de asignación de montón para el espacio necesario tanto para el bloque de control como para los datos. En el otro caso, new Obj("foo")
invoca una asignación de montón para los datos administrados y el std::shared_ptr
constructor realiza otro para el bloque de control.
Para obtener más información, consulte las notas de implementación en cppreference .
Actualización I: excepción-seguridad
NOTA (30/08/2019) : Esto no es un problema desde C ++ 17, debido a los cambios en el orden de evaluación de los argumentos de la función. Específicamente, se requiere que cada argumento de una función se ejecute completamente antes de evaluar otros argumentos.
Dado que el OP parece estar preguntándose sobre el lado de seguridad de excepción de las cosas, he actualizado mi respuesta.
Considera este ejemplo,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
Debido a que C ++ permite un orden arbitrario de evaluación de subexpresiones, un posible orden es:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Ahora, supongamos que obtenemos una excepción en el paso 2 (p. Ej., Excepción de memoria insuficiente, el Rhs
constructor arrojó alguna excepción). Luego perdemos la memoria asignada en el paso 1, ya que nada habrá tenido la oportunidad de limpiarla. El núcleo del problema aquí es que el puntero sin formato no se pasó al std::shared_ptr
constructor de inmediato.
Una forma de solucionar esto es hacerlo en líneas separadas para que no pueda ocurrir este orden arbitrario.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
La forma preferida de resolver esto, por supuesto, es usar std::make_shared
en su lugar.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Actualización II: Desventaja de std::make_shared
Citando los comentarios de Casey :
Como solo hay una asignación, la memoria del usuario no se puede desasignar hasta que el bloque de control ya no esté en uso. A weak_ptr
puede mantener vivo el bloque de control indefinidamente.
¿Por qué las instancias de weak_ptr
s mantienen vivo el bloque de control?
Debe haber una forma para que weak_ptr
s determine si el objeto administrado sigue siendo válido (por ejemplo, for lock
). Lo hacen comprobando el número de shared_ptr
s que poseen el objeto gestionado, que se almacena en el bloque de control. El resultado es que los bloques de control están vivos hasta que el shared_ptr
conteo y el weak_ptr
conteo lleguen a 0.
De regreso std::make_shared
Dado que std::make_shared
realiza una única asignación de almacenamiento dinámico tanto para el bloque de control como para el objeto gestionado, no hay forma de liberar la memoria para el bloque de control y el objeto gestionado de forma independiente. Debemos esperar hasta que podamos liberar tanto el bloque de control como el objeto administrado, lo que sucede hasta que no haya shared_ptr
s o weak_ptr
s vivos.
Supongamos que en vez realizado dos heap-asignaciones para el bloque de control y el objeto gestionado a través de new
y shared_ptr
constructor. Luego liberamos la memoria para el objeto administrado (tal vez antes) cuando no hay shared_ptr
s vivos, y liberamos la memoria para el bloque de control (tal vez más tarde) cuando no hay weak_ptr
s vivos.