Eu tenho algum código em um cabeçalho que se parece com isso:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
Se eu incluir esse cabeçalho em um cpp que não inclua a Thing
definição de tipo, ele não será compilado no VS2010-SP1:
1> C: \ Arquivos de Programas (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): erro C2027: uso do tipo indefinido 'Coisa'
Substitua std::unique_ptr
por std::shared_ptr
e compila.
Então, acho que é a std::unique_ptr
implementação atual do VS2010 que requer a definição completa e é totalmente dependente da implementação.
Ou é? Há algo em seus requisitos padrão que impossibilita std::unique_ptr
a implementação de trabalhar apenas com uma declaração de encaminhamento? Parece estranho, pois só deve segurar um ponteiro Thing
, não deveria?
c++
visual-studio-2010
c++11
stl
unique-ptr
Klaim
fonte
fonte
shared_ptr
/unique_ptr
" de Howard Hinnant. A tabela no final deve responder à sua pergunta.Respostas:
Adotado a partir daqui .
A maioria dos modelos na biblioteca padrão C ++ exige que eles sejam instanciados com tipos completos. No entanto
shared_ptr
eunique_ptr
são exceções parciais . Alguns, mas nem todos os seus membros podem ser instanciados com tipos incompletos. A motivação para isso é oferecer suporte a expressões como pimpl usando ponteiros inteligentes e sem arriscar um comportamento indefinido.O comportamento indefinido pode ocorrer quando você tem um tipo incompleto e o invoca
delete
:O código acima é legal. Ele irá compilar. Seu compilador pode ou não emitir um aviso para o código acima, como o acima. Quando executado, coisas ruins provavelmente vão acontecer. Se você tiver muita sorte, seu programa falhará. No entanto, um resultado mais provável é que seu programa vaze memória silenciosamente, pois
~A()
não será chamado.Usar
auto_ptr<A>
no exemplo acima não ajuda. Você ainda tem o mesmo comportamento indefinido como se tivesse usado um ponteiro bruto.No entanto, o uso de classes incompletas em certos lugares é muito útil! Aqui é onde
shared_ptr
eunique_ptr
ajuda. O uso de um desses ponteiros inteligentes permitirá que você continue com um tipo incompleto, exceto onde for necessário ter um tipo completo. E o mais importante, quando é necessário ter um tipo completo, você recebe um erro em tempo de compilação se tentar usar o ponteiro inteligente com um tipo incompleto nesse ponto.Não há mais comportamento indefinido:
Se seu código for compilado, você utilizou um tipo completo em todos os lugares que precisar.
shared_ptr
eunique_ptr
exige um tipo completo em lugares diferentes. Os motivos são obscuros, relacionados a um deleter dinâmico versus um deleter estático. As razões precisas não são importantes. De fato, na maioria dos códigos, não é realmente importante que você saiba exatamente onde um tipo completo é necessário. Basta codificar e, se você errar, o compilador lhe dirá.No entanto, caso seja útil, aqui está uma tabela que documenta vários membros
shared_ptr
eunique_ptr
com relação aos requisitos de integridade. Se o membro exigir um tipo completo, a entrada terá um "C", caso contrário, a entrada da tabela será preenchida com "I".Quaisquer operações que requeiram conversões de ponteiro requerem tipos completos para ambos
unique_ptr
eshared_ptr
.O
unique_ptr<A>{A*}
construtor pode se livrar de um incompletoA
apenas se o compilador não precisar configurar uma chamada para~unique_ptr<A>()
. Por exemplo, se você colocar ounique_ptr
heap, poderá se livrar de um incompletoA
. Mais detalhes sobre esse ponto podem ser encontrados na resposta de BarryTheHatchet aqui .fonte
unique_ptr
como variável de membro de uma classe, apenas declare explicitamente um destruidor (e construtor) na declaração da classe (no arquivo de cabeçalho) e prossiga para defini- los no arquivo de origem (e coloque o cabeçalho com a declaração completa da classe apontada no arquivo de origem) para impedir que o compilador incline automaticamente o construtor ou destruidor no arquivo de cabeçalho (que dispara o erro). stackoverflow.com/a/13414884/368896 também ajuda a me lembrar disso.O compilador precisa da definição de Thing para gerar o destruidor padrão para MyClass. Se você declarar explicitamente o destruidor e mover sua implementação (vazia) para o arquivo CPP, o código deverá ser compilado.
fonte
MyClass::~MyClass() = default;
no arquivo de implementação, parece menos provável que seja removido inadvertidamente mais tarde no caminho por alguém que suponha que o corpo do destruidor tenha sido apagado em vez de deliberadamente deixado em branco.default
ed eddelete
.MyClass::~MyClass() = default
que não o move para o arquivo de implementação no Clang. (ainda?)Isso não depende da implementação. O motivo pelo qual ele funciona é porque
shared_ptr
determina o destruidor correto a ser chamado no tempo de execução - não faz parte da assinatura do tipo. No entanto,unique_ptr
o destruidor de faz parte de seu tipo e deve ser conhecido em tempo de compilação.fonte
Parece que as respostas atuais não são exatamente exatamente por que o construtor padrão (ou o destruidor) é um problema, mas as vazias declaradas no cpp não são.
Aqui está o que está acontecendo:
Se a classe externa (ou seja, MyClass) não tiver construtor ou destruidor, o compilador gerará os padrões. O problema é que o compilador essencialmente insere o construtor / destruidor vazio padrão no arquivo .hpp. Isso significa que o código do construtor / destruidor padrão é compilado junto com o binário do executável do host, não com os binários da sua biblioteca. No entanto, essas definições não podem realmente construir as classes parciais. Portanto, quando o vinculador entra no binário da sua biblioteca e tenta obter o construtor / destruidor, ele não encontra nenhum e você recebe um erro. Se o código do construtor / destruidor estava no seu .cpp, o binário da sua biblioteca tem esse disponível para vinculação.
Isso não tem nada a ver com o uso de unique_ptr ou shared_ptr e outras respostas parecem ser possíveis erros confusos no antigo VC ++ para implementação de unique_ptr (o VC ++ 2015 funciona bem na minha máquina).
A moral da história é que seu cabeçalho precisa permanecer livre de qualquer definição de construtor / destruidor. Só pode conter a declaração deles. Por exemplo,
~MyClass()=default;
no hpp não funcionará. Se você permitir que o compilador insira o construtor ou destruidor padrão, você receberá um erro no vinculador.Outra observação: Se você ainda estiver recebendo esse erro mesmo depois de ter o construtor e o destruidor no arquivo cpp, provavelmente o motivo é que sua biblioteca não está sendo compilada corretamente. Por exemplo, uma vez eu simplesmente mudei o tipo de projeto de Console para Biblioteca no VC ++ e recebi esse erro porque o VC ++ não adicionou o símbolo de pré-processador _LIB e produziu exatamente a mesma mensagem de erro.
fonte
Apenas para completar:
Cabeçalho: Ah
Fonte A.cpp:
A definição de classe B deve ser vista pelo construtor, destruidor e qualquer coisa que possa excluir implicitamente B. (Embora o construtor não apareça na lista acima, no VS2017, mesmo o construtor precisa da definição de B. E isso faz sentido ao considerar que, no caso de uma exceção no construtor, o unique_ptr seja destruído novamente.)
fonte
A definição completa da Coisa é necessária no momento da instanciação do modelo. Esta é a razão exata pela qual o idioma pimpl é compilado.
Se não fosse possível, as pessoas não fariam perguntas como esta .
fonte
A resposta simples é apenas usar shared_ptr.
fonte
Quanto a mim,
Basta incluir o cabeçalho ...
fonte