Adoptado desde aquí .
La mayoría de las plantillas en la biblioteca estándar de C ++ requieren que se instancian con tipos completos. Sin embargo shared_ptr
y unique_ptr
son excepciones parciales . Algunos, pero no todos sus miembros pueden ser instanciados con tipos incompletos. La motivación para esto es apoyar modismos como pimpl usando punteros inteligentes y sin arriesgar comportamientos indefinidos.
Un comportamiento indefinido puede ocurrir cuando tiene un tipo incompleto y lo llama delete
:
class A;
A* a = ...;
delete a;
Lo anterior es un código legal. Se compilará. Su compilador puede o no emitir una advertencia para el código anterior como el anterior. Cuando se ejecuta, probablemente sucederán cosas malas. Si tienes mucha suerte, tu programa se bloqueará. Sin embargo, un resultado más probable es que su programa perderá silenciosamente la memoria como ~A()
no se llamará.
Usar auto_ptr<A>
en el ejemplo anterior no ayuda. Todavía obtienes el mismo comportamiento indefinido como si hubieras usado un puntero sin formato.
Sin embargo, ¡usar clases incompletas en ciertos lugares es muy útil! Aquí es donde shared_ptr
y unique_ptr
ayuda. El uso de uno de estos punteros inteligentes le permitirá escapar con un tipo incompleto, excepto cuando sea necesario tener un tipo completo. Y lo más importante, cuando es necesario tener un tipo completo, obtiene un error en tiempo de compilación si intenta usar el puntero inteligente con un tipo incompleto en ese punto.
No más comportamientos indefinidos:
Si su código se compila, entonces ha utilizado un tipo completo en todas partes donde lo necesita.
class A
{
class impl;
std::unique_ptr<impl> ptr_; // ok!
public:
A();
~A();
// ...
};
shared_ptr
y unique_ptr
requieren un tipo completo en diferentes lugares. Las razones son oscuras y tienen que ver con un eliminador dinámico frente a un eliminador estático. Las razones precisas no son importantes. De hecho, en la mayoría de los códigos no es realmente importante que sepa exactamente dónde se requiere un tipo completo. Solo codifique, y si se equivoca, el compilador le dirá.
Sin embargo, en caso de que le sea útil, aquí hay una tabla que documenta a varios miembros shared_ptr
y unique_ptr
con respecto a los requisitos de integridad. Si el miembro requiere un tipo completo, la entrada tiene una "C"; de lo contrario, la entrada de la tabla se rellena con "I".
Complete type requirements for unique_ptr and shared_ptr
unique_ptr shared_ptr
+------------------------+---------------+---------------+
| P() | I | I |
| default constructor | | |
+------------------------+---------------+---------------+
| P(const P&) | N/A | I |
| copy constructor | | |
+------------------------+---------------+---------------+
| P(P&&) | I | I |
| move constructor | | |
+------------------------+---------------+---------------+
| ~P() | C | I |
| destructor | | |
+------------------------+---------------+---------------+
| P(A*) | I | C |
+------------------------+---------------+---------------+
| operator=(const P&) | N/A | I |
| copy assignment | | |
+------------------------+---------------+---------------+
| operator=(P&&) | C | I |
| move assignment | | |
+------------------------+---------------+---------------+
| reset() | C | I |
+------------------------+---------------+---------------+
| reset(A*) | C | C |
+------------------------+---------------+---------------+
Cualquier operación que requiera conversiones de puntero requiere tipos completos para ambos unique_ptr
y shared_ptr
.
El unique_ptr<A>{A*}
constructor puede salirse con una incompleta A
solo si no se requiere que el compilador configure una llamada a ~unique_ptr<A>()
. Por ejemplo, si coloca el unique_ptr
en el montón, puede salirse con una incompleta A
. Se pueden encontrar más detalles sobre este punto en la respuesta de BarryTheHatchet aquí .
shared_ptr
/unique_ptr
" de Howard Hinnant. La tabla al final debe responder a su pregunta.