std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Muchas publicaciones de google y stackoverflow están ahí, pero no puedo entender por qué make_shared
es más eficiente que usarlo directamente shared_ptr
.
¿Alguien puede explicarme paso a paso la secuencia de objetos creados y las operaciones realizadas por ambos para que pueda entender cómo make_shared
es eficiente? He dado un ejemplo arriba como referencia.
c++
c++11
shared-ptr
Anup Buchke
fuente
fuente
make_shared
usted puede escribirauto p1(std::make_shared<A>())
y p1 tendrá el tipo correcto.Respuestas:
La diferencia es que
std::make_shared
realiza una asignación de montón, mientras que llamar alstd::shared_ptr
constructor realiza dos.¿Dónde ocurren las asignaciones del montón?
std::shared_ptr
gestiona dos entidades: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 elstd::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,
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ó alstd::shared_ptr
constructor de inmediato.Una forma de solucionar esto es hacerlo en líneas separadas para que no pueda ocurrir este orden arbitrario.
La forma preferida de resolver esto, por supuesto, es usar
std::make_shared
en su lugar.Actualización II: Desventaja de
std::make_shared
Citando los comentarios de Casey :
¿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, forlock
). Lo hacen comprobando el número deshared_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 elshared_ptr
conteo y elweak_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 hayashared_ptr
s oweak_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
yshared_ptr
constructor. Luego liberamos la memoria para el objeto administrado (tal vez antes) cuando no hayshared_ptr
s vivos, y liberamos la memoria para el bloque de control (tal vez más tarde) cuando no hayweak_ptr
s vivos.fuente
make_shared
: dado que solo hay una asignación, la memoria del usuario no se puede desasignar hasta que el bloque de control ya no esté en uso. Aweak_ptr
puede mantener vivo el bloque de control indefinidamente.make_shared
ymake_unique
consistentemente, no tendrá que tener punteros crudos y puede tratar cada ocurrencianew
como un olor a código.shared_ptr
y noweak_ptr
s, llamarreset()
a lashared_ptr
instancia eliminará el bloque de control. Pero esto es independientemente o simake_shared
se utilizó. El usomake_shared
marca la diferencia porque podría prolongar la vida útil de la memoria asignada para el objeto administrado . Cuando elshared_ptr
recuento golpea 0, el destructor del objeto gestionado es llamado independientemente demake_shared
, pero liberando su memoria sólo puede hacerse simake_shared
se no se utiliza. Espero que esto lo aclare más.El puntero compartido gestiona tanto el objeto en sí como un objeto pequeño que contiene el recuento de referencia y otros datos de mantenimiento.
make_shared
puede asignar un solo bloque de memoria para contener ambos; La construcción de un puntero compartido de un puntero a un objeto ya asignado deberá asignar un segundo bloque para almacenar el recuento de referencia.Además de esta eficiencia, el uso
make_shared
significa que no necesita tratar connew
punteros sin procesar, lo que brinda una mejor seguridad de excepción: no hay posibilidad de lanzar una excepción después de asignar el objeto, pero antes de asignarlo al puntero inteligente.fuente
Hay otro caso en el que las dos posibilidades difieren, además de las ya mencionadas: si necesita llamar a un constructor no público (protegido o privado), make_shared podría no poder acceder a él, mientras que la variante con el nuevo funciona bien .
fuente
new
, de lo contrario lo habría usadomake_shared
. Aquí hay una pregunta relacionada al respecto: stackoverflow.com/questions/8147027/… .Si necesita una alineación de memoria especial en el objeto controlado por shared_ptr, no puede confiar en make_shared, pero creo que es la única buena razón para no usarlo.
fuente
Veo un problema con std :: make_shared, no es compatible con constructores privados / protegidos
fuente
Shared_ptr
: Realiza dos asignaciones de montónMake_shared
: Realiza solo una asignación de almacenamiento dinámicofuente
Sobre la eficiencia y el tiempo dedicado a la asignación, realicé esta simple prueba a continuación, creé muchas instancias a través de estas dos formas (una a la vez):
La cuestión es que usar make_shared tomó el doble de tiempo en comparación con usar new. Entonces, usando new hay dos asignaciones de montón en lugar de una usando make_shared. Tal vez esta es una prueba estúpida, pero ¿no muestra que usar make_shared lleva más tiempo que usar new? Por supuesto, solo estoy hablando del tiempo utilizado.
fuente
Creo que la parte de seguridad de excepción de la respuesta de mr mpark sigue siendo una preocupación válida. al crear un shared_ptr como este: shared_ptr <T> (nueva T), la nueva T puede tener éxito, mientras que la asignación del bloque de control de shared_ptr puede fallar. en este escenario, la T recién asignada se perderá, ya que shared_ptr no tiene forma de saber que se creó en el lugar y es seguro eliminarla. ¿O me estoy perdiendo algo? No creo que las reglas más estrictas sobre la evaluación de parámetros de función ayuden de ninguna manera aquí ...
fuente