Tengo un código en un encabezado que se ve así:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
Si incluyo este encabezado en un cpp que no incluye la Thing
definición de tipo, entonces esto no se compila en VS2010-SP1:
1> C: \ Archivos de programa (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): error C2027: uso de tipo indefinido 'Thing'
Reemplazar std::unique_ptr
por std::shared_ptr
y se compila.
Entonces, supongo que es la std::unique_ptr
implementación actual del VS2010 la que requiere la definición completa y depende totalmente de la implementación.
¿O es eso? ¿Hay algo en sus requisitos estándar que hace imposible que std::unique_ptr
la implementación funcione solo con una declaración directa? Se siente extraño ya que solo debería contener un puntero Thing
, ¿no?
shared_ptr
/unique_ptr
" de Howard Hinnant. La tabla al final debe responder a su pregunta.Respuestas:
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
yunique_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
: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
yunique_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.
shared_ptr
yunique_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
yunique_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".Cualquier operación que requiera conversiones de puntero requiere tipos completos para ambos
unique_ptr
yshared_ptr
.El
unique_ptr<A>{A*}
constructor puede salirse con una incompletaA
solo si no se requiere que el compilador configure una llamada a~unique_ptr<A>()
. Por ejemplo, si coloca elunique_ptr
en el montón, puede salirse con una incompletaA
. Se pueden encontrar más detalles sobre este punto en la respuesta de BarryTheHatchet aquí .fuente
unique_ptr
como una variable miembro de una clase, simplemente explícitamente declarar un destructor (y constructor) en la declaración de clase (en el archivo de cabecera) y proceder a definir las en el archivo de origen (y coloque el encabezado con la declaración completa de la clase apuntada en el archivo de origen) para evitar que el compilador incluya automáticamente el constructor o destructor en el archivo de encabezado (que desencadena el error). stackoverflow.com/a/13414884/368896 también me ayuda a recordar esto.El compilador necesita la definición de Thing para generar el destructor predeterminado para MyClass. Si declara explícitamente el destructor y mueve su implementación (vacía) al archivo CPP, el código debe compilarse.
fuente
MyClass::~MyClass() = default;
en el archivo de implementación, parece menos probable que alguien que asuma que el cuerpo del dispositivo se borró accidentalmente en lugar de dejarlo en blanco deliberadamente lo elimine inadvertidamente.default
eddelete
yd.MyClass::~MyClass() = default
que no lo mueve al archivo de implementación en Clang. (¿todavía?)Esto no depende de la implementación. La razón por la que funciona es porque
shared_ptr
determina el destructor correcto para llamar en tiempo de ejecución: no es parte de la firma de tipo. Sin embargo,unique_ptr
el destructor es parte de su tipo y debe conocerse en tiempo de compilación.fuente
Parece que las respuestas actuales no están aclarando exactamente por qué el constructor predeterminado (o destructor) es un problema, pero las vacías declaradas en cpp no lo son.
Aquí está lo que está sucediendo:
Si la clase externa (es decir, MyClass) no tiene constructor o destructor, el compilador genera los predeterminados. El problema con esto es que el compilador esencialmente inserta el constructor / destructor vacío predeterminado en el archivo .hpp. Esto significa que el código para el contructor / destructor predeterminado se compila junto con el binario del ejecutable del host, no junto con los binarios de su biblioteca. Sin embargo, estas definiciones realmente no pueden construir las clases parciales. Entonces, cuando el enlazador va en el binario de su biblioteca e intenta obtener el constructor / destructor, no encuentra ninguno y obtiene un error. Si el código del constructor / destructor estaba en su .cpp, entonces el binario de su biblioteca lo tiene disponible para vincular.
Esto no tiene nada que ver con el uso de unique_ptr o shared_ptr y otras respuestas parecen ser posibles errores confusos en el antiguo VC ++ para la implementación de unique_ptr (VC ++ 2015 funciona bien en mi máquina).
Entonces, la moraleja de la historia es que su encabezado debe permanecer libre de cualquier definición de constructor / destructor. Solo puede contener su declaración. Por ejemplo,
~MyClass()=default;
en hpp no funcionará. Si permite que el compilador inserte un constructor o destructor predeterminado, obtendrá un error de vinculador.Otra nota al margen: si aún recibe este error incluso después de tener el constructor y el destructor en el archivo cpp, lo más probable es que su biblioteca no se esté compilando correctamente. Por ejemplo, una vez simplemente cambié el tipo de proyecto de Consola a Biblioteca en VC ++ y obtuve este error porque VC ++ no agregó el símbolo del preprocesador _LIB y eso produjo exactamente el mismo mensaje de error.
fuente
Solo para completar:
Encabezado: Ah
Fuente A.cpp:
La definición de la clase B debe ser vista por el constructor, el destructor y cualquier cosa que pueda eliminar implícitamente B. (Aunque el constructor no aparece en la lista anterior, en VS2017 incluso el constructor necesita la definición de B. Y esto tiene sentido cuando se considera que en caso de una excepción en el constructor, el unique_ptr se destruye nuevamente).
fuente
Se requiere la definición completa de la Cosa en el punto de creación de instancias de plantilla. Esta es la razón exacta por la cual compila el modismo de pimpl.
Si no fuera posible, la gente no haría preguntas como esta .
fuente
La respuesta simple es simplemente usar shared_ptr en su lugar.
fuente
Como para mí,
Solo incluye el encabezado ...
fuente