c++ - smart - ¿Es mejor usar shared_ptr.reset o operator=?
unique pointer (2)
Correcto, reset(new T...)
sufre todos los problemas de shared_ptr(new T...)
; dará como resultado una doble asignación y además no es seguro para excepciones (no hay muchas posibilidades de una fuga, a menos que suceda bad_alloc
dentro del reset
).
reset
se documenta como equivalente a shared_ptr<T>(ptr).swap(*this)
, por lo que también podría escribir:
make_shared<T>(args...).swap(mysharedptr);
La asignación de make_shared<T>
es casi equivalente, la única diferencia es el orden relativo de la eliminación de la antigua T
y la destrucción de la shared_ptr
temporal, que no es observable.
Estoy tratando de envolver mi cabeza en torno a los nuevos modismos para C ++ 11.
Parece que con shared_ptr al menos, hay una diferencia sustancial entre el uso de new T()
y make_shared<T>()
.
Pero, ¿qué hay de restablecer un puntero compartido para apuntar a una nueva instancia de algo? Anteriormente, normalmente usaría el miembro reset(new T())
. Sin embargo, ¿esto no tiene el mismo problema que no usar make_shared () en primer lugar? (es decir, no permite que make_shared asigne el objeto, por lo tanto, ¿se ve obligado a colocar el recuento de referencia en una asignación separada en lugar de en la misma asignación que la propia T?)
Es simplemente mejor seguir adelante para usar:
mysharedptr = make_shared<T>(args...);
¿O hay un mejor camino?
¿Y no debería restablecer el reenvío variadic de la oferta como lo hace make_shared, para que uno pueda escribir mysharedptr.reset (args ...) ;?
De hecho, hay una diferencia sustancial entre:
shared_ptr<T> sp(new T());
Y:
shared_ptr<T> sp = make_shared<T>();
La primera versión realiza una asignación para el objeto T
, luego realiza una asignación separada para crear el contador de referencia. La segunda versión realiza una asignación única tanto para el objeto como para el contador de referencia, colocándolos en una región contigua de la memoria, lo que resulta en una menor sobrecarga de memoria.
Además, algunas implementaciones pueden realizar optimizaciones de espacio adicionales en el caso de make_shared<>
(consulte la optimización "Sabemos dónde vive" realizada por la implementación de MS).
Sin embargo, esa no es la única razón por la que make_shared<>
existe. La versión basada en la new T()
explícita no es segura en algunas situaciones, especialmente cuando se invoca una función que acepta un shared_ptr
.
void f(shared_ptr<T> sp1, shared_ptr<T> sp2);
...
f(shared_ptr<T>(new T()), shared_ptr<T>(new T()))
Aquí, el compilador podría evaluar la primera new T()
expresión new T()
, luego evaluar la segunda new T()
expresión new T()
y luego construir los objetos shared_ptr<>
correspondientes. Pero, ¿qué shared_ptr<>
si la segunda asignación provoca una excepción antes de que el primer objeto asignado esté vinculado a su shared_ptr<>
? Se filtrará. Con make_shared<>()
, esto no es posible:
f(make_shared<T>(), make_shared<T>())
Debido a que los objetos asignados están vinculados a los respectivos objetos shared_ptr<>
dentro de cada llamada de función a make_shared<>()
, esta llamada es segura de excepciones. Esta es otra razón por la que nunca se debe usar lo new
desnudo a menos que realmente sepa lo que está haciendo. (*)
Teniendo en cuenta su comentario sobre reset()
, tiene razón al observar que reset(new T())
realizará asignaciones separadas para el contador y el objeto, al igual que la construcción de un shared_ptr<>
nuevo realizará una asignación separada cuando un raw puntero se pasa como un argumento. Por lo tanto, es preferible una asignación que use make_shared<>
(o incluso una declaración como reset(make_shared<T>())
).
Sea o no reset()
debería admitir una lista de argumentos variadic, esto es probablemente más de un tipo de discusión abierta para la que no es una buena opción.
(*) Hay algunas situaciones que aún lo requieren. Por ejemplo, el hecho de que la biblioteca estándar de C ++ carece de la función make_unique<>
correspondiente para unique_ptr
, por lo que tendrá que escribir una usted mismo. Otra situación es cuando no desea que el objeto y el contador se asignen en un solo bloque de memoria, ya que la presencia de punteros débiles en el objeto evitará que todo el bloque se desasigne, aunque no haya más punteros que posean el objeto. .