La diferencia es que std::make_sharedrealiza una asignación de montón, mientras que llamar al std::shared_ptrconstructor 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_sharedrealiza 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_ptrconstructor 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 Rhsconstructor 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_ptrconstructor 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_shareden 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_ptrpuede mantener vivo el bloque de control indefinidamente.
¿Por qué las instancias de weak_ptrs mantienen vivo el bloque de control?
Debe haber una forma para que weak_ptrs determine si el objeto administrado sigue siendo válido (por ejemplo, for lock). Lo hacen comprobando el número de shared_ptrs 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_ptrconteo y el weak_ptrconteo lleguen a 0.
De regreso std::make_shared
Dado que std::make_sharedrealiza 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_ptrs o weak_ptrs vivos.
Supongamos que en vez realizado dos heap-asignaciones para el bloque de control y el objeto gestionado a través de newy shared_ptrconstructor. Luego liberamos la memoria para el objeto administrado (tal vez antes) cuando no hay shared_ptrs vivos, y liberamos la memoria para el bloque de control (tal vez más tarde) cuando no hay weak_ptrs vivos.