punteros inteligentes (impulso) explicados


220

¿Cuál es la diferencia entre el siguiente conjunto de punteros? ¿Cuándo utiliza cada puntero en el código de producción, si es que lo hace?

¡Los ejemplos serían apreciados!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

¿Usas boost en el código de producción?

Respuestas:


339

Propiedades básicas de los punteros inteligentes.

Es fácil cuando tiene propiedades que puede asignar a cada puntero inteligente. Hay tres propiedades importantes.

  • sin propiedad en absoluto
  • transferencia de la propiedad
  • cuota de propiedad

El primero significa que un puntero inteligente no puede eliminar el objeto, porque no lo posee. El segundo significa que solo un puntero inteligente puede apuntar al mismo objeto al mismo tiempo. Si el puntero inteligente se va a devolver de las funciones, la propiedad se transfiere al puntero inteligente devuelto, por ejemplo.

El tercero significa que múltiples punteros inteligentes pueden apuntar al mismo objeto al mismo tiempo. Esto también se aplica a un puntero sin formato , sin embargo, los punteros sin formato carecen de una característica importante: no definen si son propietarios o no. Un puntero inteligente de propiedad compartida eliminará el objeto si cada propietario abandona el objeto. Este comportamiento es a menudo necesario, por lo que los punteros inteligentes de propiedad compartida están muy extendidos.

Algunos propietarios de punteros inteligentes no admiten ni el segundo ni el tercero. Por lo tanto, no se pueden devolver de las funciones o pasar a otro lugar. Cuál es el más adecuado para RAIIfines en los que el puntero inteligente se mantiene local y se crea para que libere un objeto después de que salga del alcance.

La participación en la propiedad se puede implementar al tener un constructor de copia. Esto, naturalmente, copia un puntero inteligente y tanto la copia como el original harán referencia al mismo objeto. La transferencia de propiedad no puede implementarse realmente en C ++ actualmente, porque no hay medios para transferir algo de un objeto a otro compatible con el lenguaje: si intenta devolver un objeto desde una función, lo que está sucediendo es que el objeto se copia. Por lo tanto, un puntero inteligente que implementa la transferencia de propiedad tiene que usar el constructor de copias para implementar esa transferencia de propiedad. Sin embargo, esto a su vez interrumpe su uso en contenedores, porque los requisitos establecen un cierto comportamiento del constructor de copia de elementos de contenedores que es incompatible con el comportamiento denominado de "constructor móvil" de estos punteros inteligentes.

C ++ 1x proporciona soporte nativo para la transferencia de propiedad mediante la introducción de los llamados "constructores de movimientos" y "operadores de asignación de movimientos". También viene con un puntero inteligente de transferencia de propiedad llamado unique_ptr.

Categorización de punteros inteligentes

scoped_ptres un puntero inteligente que no es transferible ni compartible. Solo se puede usar si necesita asignar memoria localmente, pero asegúrese de que se libere nuevamente cuando salga del alcance. Pero aún puede intercambiarse con otro scoped_ptr, si lo desea.

shared_ptres un puntero inteligente que comparte la propiedad (tercer tipo arriba). Se cuenta como referencia para que pueda ver cuándo la última copia queda fuera de alcance y luego libera el objeto administrado.

weak_ptres un puntero inteligente no propietario. Se usa para hacer referencia a un objeto administrado (administrado por shared_ptr) sin agregar un recuento de referencia. Normalmente, necesitaría obtener el puntero sin procesar de shared_ptr y copiarlo. Pero eso no sería seguro, ya que no tendría una forma de verificar cuándo se eliminó realmente el objeto. Entonces, weak_ptr proporciona medios al hacer referencia a un objeto administrado por shared_ptr. Si necesita acceder al objeto, puede bloquear la administración del mismo (para evitar que en otro hilo un shared_ptr lo libere mientras usa el objeto) y luego usarlo. Si weak_ptr apunta a un objeto ya eliminado, lo notará lanzando una excepción. El uso de weak_ptr es más beneficioso cuando tiene una referencia cíclica: el conteo de referencias no puede hacer frente fácilmente a tal situación.

intrusive_ptres como un shared_ptr, pero no mantiene el recuento de referencia en shared_ptr, sino que deja aumentar / disminuir el recuento a algunas funciones auxiliares que deben definirse por el objeto que se administra. Esto tiene la ventaja de que un objeto ya referenciado (que tiene un recuento de referencia incrementado por un mecanismo de recuento de referencia externo) puede insertarse en un intrusive_ptr, porque el recuento de referencia ya no es interno en el puntero inteligente, pero el puntero inteligente usa un existente Mecanismo de recuento de referencia.

unique_ptres un puntero de transferencia de propiedad. No puede copiarlo, pero puede moverlo utilizando los constructores de movimiento de C ++ 1x:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

Esta es la semántica que std :: auto_ptr obedece, pero debido a la falta de soporte nativo para moverse, no puede proporcionarlos sin trampas. unique_ptr robará automáticamente recursos de otro temporal unique_ptr, que es una de las características clave de la semántica de movimiento. auto_ptr quedará en desuso en la próxima versión estándar de C ++ a favor de unique_ptr. C ++ 1x también permitirá rellenar objetos que solo se pueden mover pero no se pueden copiar en contenedores. Por lo tanto, puede insertar unique_ptr en un vector, por ejemplo. Me detendré aquí y lo referiré a un buen artículo sobre esto si desea leer más sobre esto.


3
Gracias por el elogio amigo. lo aprecio, así que ahora también obtendrás +1: p
Johannes Schaub - litb

@litb: Tengo una duda en "transferencia de propiedad"; Estoy de acuerdo en que no hay una transferencia real de propiedad entre los objetos en C ++ 03, pero para los punteros inteligentes no se puede hacer esto, por el mecanismo de copia destructiva que se indica aquí informmit.com/articles/article.aspx?p=31529&seqNum= 5 .
legends2k

3
Fantástica respuesta. Nota: auto_ptrya está en desuso (C ++ 11).
nickolay

2
"Esto a su vez interrumpe su uso en los contenedores, porque los requisitos establecen un cierto comportamiento del constructor de copia de los elementos de los contenedores que es incompatible con el llamado comportamiento del" constructor en movimiento "de estos punteros inteligentes". No entendí esa parte.
Raja

También me han dicho que intrusive_ptrpuede ser preferible para shared_ptruna mejor coherencia de caché. Aparentemente, el caché funciona mejor si almacena el recuento de referencia como parte de la memoria del objeto administrado en lugar de un objeto separado. Esto se puede implementar en una plantilla o superclase del objeto administrado.
Eliot

91

scoped_ptr es el más simple. Cuando se sale del alcance, se destruye. El siguiente código es ilegal (scoped_ptrs no son copiables) pero ilustrará un punto:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr es una referencia contada. Cada vez que se produce una copia o asignación, el recuento de referencia se incrementa. Cada vez que se dispara el destructor de una instancia, el recuento de referencia para el T * sin procesar se reduce. Una vez que es 0, el puntero se libera.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

weak_ptr es una referencia débil a un puntero compartido que requiere que verifiques si el shared_ptr apuntado aún está presente

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr se usa generalmente cuando hay un ptr inteligente de terceros que debe usar. Llamará a una función gratuita para agregar y disminuir el recuento de referencias. Consulte el enlace para aumentar la documentación para obtener más información.


no se if (tPtrAccessed[0].get() == 0)supone que es if (tPtrAccessed.get() == 0) ?
Rajeshwar

@DougT. ¿Crees que Java usa la misma idea con las referencias? ¿Suave, duro, débil, etc.?
gansub

20

No pase boost::ptr_containerpor alto en ninguna encuesta de impulso de punteros inteligentes. Pueden ser invaluables en situaciones donde, por ejemplo std::vector<boost::shared_ptr<T> >, sería demasiado lento.


En realidad, la última vez que lo probé, el benchmarking mostró que la brecha de rendimiento se había reducido significativamente desde que escribí esto originalmente, ¡al menos en PC HW típico! Sin embargo, el enfoque ptr_container más eficiente aún puede tener algunas ventajas en casos de uso de nicho.
2016

12

Secundo el consejo sobre mirar la documentación. No es tan aterrador como parece. Y algunas pistas cortas:

  • scoped_ptr- un puntero se elimina automáticamente cuando sale del alcance. Nota: no es posible la asignación, pero no introduce gastos generales
  • intrusive_ptr- puntero de recuento de referencia sin sobrecarga de smart_ptr. Sin embargo, el objeto en sí mismo almacena el recuento de referencia
  • weak_ptr - trabaja junto con shared_ptr las situaciones que resultan en dependencias circulares (lea la documentación y busque en google una buena imagen;)
  • shared_ptr - el genérico, el más poderoso (y pesado) de los punteros inteligentes (de los ofrecidos por boost)
  • También existe el antiguo auto_ptr, que asegura que el objeto al que apunta se destruye automáticamente cuando el control deja un alcance. Sin embargo, tiene una semántica de copia diferente al resto de los chicos.
  • unique_ptr- vendrá con C ++ 0x

Respuesta a la edición:


8
Vine aquí porque la documentación de impulso me pareció demasiado aterradora.
Francois Botha
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.