¿Se requiere std :: unique_ptr para conocer la definición completa de T?

Tengo un código en un encabezado que se ve así:

#include  class Thing; class MyClass { std::unique_ptr my_thing; }; 

Si incluyo este encabezado en una cpp que no incluye la definición de tipo Thing , entonces esto no se comstack bajo VS2010-SP1:

1> C: \ Archivos de progtwig (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): error C2027: uso del tipo indefinido ‘Thing’

Reemplazar std::unique_ptr por std::shared_ptr y comstack.

Por lo tanto, supongo que es la implementación actual de VS2010 std::unique_ptr 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 haga imposible que la implementación de std::unique_ptr funcione solo con una statement directa? Se siente extraño, ya que solo debería tener un puntero a Thing , ¿no es así?

Adoptado desde aquí .

La mayoría de las plantillas en la biblioteca estándar de C ++ requieren que se creen instancias 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 expresiones idiomáticas tales como pimpl usando punteros inteligentes, y sin arriesgar comportamientos indefinidos.

El comportamiento indefinido puede ocurrir cuando tiene un tipo incompleto y llama a delete en él:

 class A; A* a = ...; delete a; 

Lo anterior es código legal. Comstackrá. Su comstackdor puede o no emitir una advertencia para el código anterior como el anterior. Cuando se ejecuta, probablemente sucedan cosas malas. Si tienes mucha suerte, tu progtwig se bloqueará. Sin embargo, un resultado más probable es que su progtwig perderá silenciosamente memoria ya que ~A() no se invocará.

Usar auto_ptr en el ejemplo anterior no ayuda. Aún 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 ayudan. El uso de uno de estos indicadores inteligentes le permitirá salirse con la suya con un tipo incompleto, excepto cuando sea necesario tener un tipo completo. Y lo más importante, cuando es necesario tener un tipo completo, se obtiene un error en tiempo de comstackción si intenta utilizar el puntero inteligente con un tipo incompleto en ese punto.

No más comportamiento indefinido:

Si tu código se comstack, entonces has usado un tipo completo donde sea que lo necesites.

 class A { class impl; std::unique_ptr ptr_; // ok! public: A(); ~A(); // ... }; 

shared_ptr y unique_ptr requieren un tipo completo en diferentes lugares. Los motivos son oscuros y tienen que ver con un eliminador dynamic 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. Simplemente codifique, y si lo hace mal, el comstackdor se lo dirá.

Sin embargo, en caso de que sea útil para usted, aquí hay una tabla que documenta varios miembros de 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 | +------------------------+---------------+---------------+ 

Las operaciones que requieren conversiones de puntero requieren tipos completos para unique_ptr y shared_ptr .

El unique_ptr{A*} puede salirse con una A incompleta solo si el comstackdor no está obligado a configurar una llamada a ~unique_ptr() . Por ejemplo, si coloca el unique_ptr en el montón, puede salirse con la A incompleta. Se pueden encontrar más detalles sobre este punto en la respuesta de BarryTheHatchet aquí .

El comstackdor 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 debería comstackrse.

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, el unique_ptr de unique_ptr es parte de su tipo y debe conocerse en tiempo de comstackción.

Parece que las respuestas actuales no están exactamente explicando por qué el constructor (o el destructor) predeterminado es un problema, pero los vacíos declarados en cpp no ​​lo son.

Aquí está lo que está sucediendo:

Si la clase externa (es decir, MyClass) no tiene constructor o destructor, entonces el comstackdor genera los predeterminados. El problema con esto es que el comstackdor esencialmente inserta el constructor / destructor vacío predeterminado en el archivo .hpp. Esto significa que el código de contructor / destructor predeterminado se comstack junto con el binario ejecutable del host, no junto con los binarios de la biblioteca. Sin embargo, estas definiciones no pueden realmente construir las clases parciales. Entonces cuando el enlazador va en el binario de tu biblioteca e intenta obtener el constructor / destructor, no encuentra ninguno y obtienes un error. Si el código constructor / destructor estaba en su .cpp, entonces su biblioteca binaria tiene el disponible para vincular.

Por lo tanto, esto no tiene nada que ver con el uso de unique_ptr en lugar de shared_ptr para el escenario anterior siempre que use comstackdores modernos (el comstackdor de VC ++ puede tener un error en la implementación de unique_ptr pero VC ++ 2015 funciona bien en mi máquina).

Tan moral de la historia es que su encabezado debe permanecer libre de cualquier definición de constructor / destructor. Solo puede contener su statement. Por ejemplo, ~MyClass()=default; en hpp no ​​funcionará. Si permite que el comstackdor inserte un constructor o destructor predeterminado, obtendrá un error de enlazador.

Otra nota al margen: si todavía está recibiendo este error incluso después de tener el constructor y el destructor en el archivo cpp, lo más probable es que la razón sea que su biblioteca no se está comstackndo correctamente. Por ejemplo, una vez simplemente cambié el tipo de proyecto de la Consola a la 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.

La definición completa de la cosa se requiere en el momento de la instanciación de la plantilla. Esta es la razón exacta por la que comstack el modismo pimpl.

Si no fuera posible, la gente no haría preguntas como esta .

Solo para completar:

Encabezado: Ah

 class B; // forward declaration class A { std::unique_ptr ptr_; // ok! public: A(); ~A(); // ... }; 

Fuente A.cpp:

 class B { ... }; // class definition A::A() { ... } A::~A() { ... } 

La definición de clase B debe ser vista por constructor, 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).

Como para mí,

 QList> controllers; 

Solo incluye el encabezado …

 #include