Estou usando o pimpl-idiom com std::unique_ptr:
No entanto, recebo um erro de compilação sobre o uso de um tipo incompleto, na linha 304 em <memory>:
Aplicativo inválido de ' sizeof' para um tipo incompleto ' uixx::window::window_impl'
Até onde eu sei, std::unique_ptrdeve poder ser usado com um tipo incompleto. Isso é um bug no libc ++ ou estou fazendo algo errado aqui?
Link de referência para os requisitos de completude: stackoverflow.com/a/6089065/576911
Um pimpl é frequentemente construído e não modificado desde então. Eu normalmente uso um std :: shared_ptr <const window_impl>
— mfnx
Relacionado: Gostaria muito de saber por que isso funciona no MSVC e como impedir que ele funcione (para que não quebre as compilações dos meus colegas do GCC).
Aqui estão alguns exemplos de std::unique_ptrtipos incompletos. O problema está na destruição.
Se você usa o pimpl com unique_ptr, precisa declarar um destruidor:
porque, caso contrário, o compilador gera um padrão e precisa de uma declaração completa de foo::impl para isso.
Se você tem construtores de modelos, está ferrado, mesmo que não construa o impl_membro:
No escopo do espaço para nome, o uso unique_ptrnão funcionará:
já que o compilador deve saber aqui como destruir esse objeto de duração estática. Uma solução alternativa é:
Acho que sua primeira solução (adicionar o foo destructor) permite que a própria declaração de classe seja compilada, mas declarar um objeto desse tipo em qualquer lugar resulta no erro original ("aplicação inválida de 'sizeof' ...").
excelente resposta, apenas para notar; ainda podemos usar o construtor / destruidor padrão colocando, por exemplo, foo::~foo() = default;no arquivo src
— assem
Uma maneira de conviver com os construtores de modelos seria declarar, mas não definir o construtor no corpo da classe, defini-lo em algum lugar em que a definição completa do implemento seja vista e instanciar explicitamente todas as instanciações necessárias.
— 31415 enobayram
Você poderia explicar como isso funcionaria em alguns casos e em outros? Eu tenho usado o idioma pimpl com um unique_ptr e uma classe sem destrutor, e em outro projeto meu código não compilar com a OP de erro mencionada ..
Parece que se o valor padrão para unique_ptr estiver definido como {nullptr} no arquivo de cabeçalho da classe com o estilo c ++ 11, uma declaração completa também será necessária pelo motivo acima.
— Feirainy 11/04/19
Como Alexandre C. mencionou, o problema se resume ao windowdestruidor de ser implicitamente definido em locais onde o tipo dewindow_impl ainda é incompleto. Além de suas soluções, outra solução alternativa que usei é declarar um functor Deleter no cabeçalho:
Observe que o uso de uma função Deleter personalizada impede o uso de std::make_unique(disponível no C ++ 14), conforme já discutido aqui .
— Fernando Costa Bertoldi
Esta é a solução correta para mim. Não é exclusivo para usar o pimpl-idiom, é um problema geral com o uso de std :: unique_ptr com classes incompletas. O deleter padrão usado por std :: unique_ptr <X> tenta "excluir X", o que não pode ser feito se X for uma declaração direta. Ao especificar uma função deleter, você pode colocar essa função em um arquivo de origem em que a classe X esteja completamente definida. Outros arquivos de origem podem usar std :: unique_ptr <X, DeleterFunc> mesmo que X seja apenas uma declaração de encaminhamento, desde que estejam vinculados ao arquivo de origem que contém DeleterFunc.
— sheltond
Essa é uma boa solução alternativa quando você deve ter uma definição de função embutida criando uma instância do seu tipo "Foo" (por exemplo, um método estático "getInstance" que faça referência ao construtor e destruidor) e você não deseja movê-las para um arquivo de implementação como @ adspx5 sugere.
— GameSalutes
use um deleter personalizado
O problema é que unique_ptr<T>deve chamar o destruidor T::~T()em seu próprio destruidor, seu operador de atribuição de movimentação e unique_ptr::reset()função de membro (apenas). No entanto, eles devem ser chamados (implícita ou explicitamente) em várias situações do PIMPL (já no destrutor da classe externa e no operador de atribuição de movimentação).
Como já apontado em outra resposta, uma forma de evitar que é mover todas as operações que requerem unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&)e unique_ptr::reset()para o arquivo de origem onde a classe pimpl helper é realmente definido.
No entanto, isso é bastante inconveniente e desafia o próprio sentido da cafeteira idoim até certo ponto. Uma solução muito mais limpa que evita tudo o que é usar um deleter personalizado e apenas move sua definição para o arquivo de origem onde vive a classe auxiliar de espinhas. Aqui está um exemplo simples:
Em vez de uma classe deleter separada, você também pode usar uma função livre ou staticmembro fooem conjunto com uma lambda:
Provavelmente você tem alguns corpos de função no arquivo .h na classe que usa o tipo incompleto.
Verifique se na janela .h da classe você tem apenas declaração de função. Todos os corpos de função da janela devem estar no arquivo .cpp. E para window_impl também ...
Btw, você precisa adicionar explicitamente a declaração de destruidor para a classe windows no seu arquivo .h.
Mas você NÃO PODE colocar o corpo vazio do dtor no seu arquivo de cabeçalho:
Deve ser apenas uma declaração:
— adspx5
Esta foi a minha solução também. Muito mais conciso. Apenas tenha seu construtor / destruidor declarado no cabeçalho e definido no arquivo cpp.
— Kris Morness 14/09/19
Para adicionar às respostas dos outros sobre o deleter personalizado, em nossa "biblioteca de utilitários" interna, adicionei um cabeçalho auxiliar para implementar esse padrão comum ( std::unique_ptrde um tipo incompleto, conhecido apenas por algumas das TU por exemplo, para evitar longos tempos de compilação ou fornecer apenas um identificador opaco para os clientes).
Ele fornece o andaime comum para esse padrão: uma classe deleter personalizada que chama uma função deleter definida externamente, um alias de tipo para a unique_ptrcom essa classe deleter e uma macro para declarar a função deleter em uma TU que possui uma definição completa do tipo. Eu acho que isso tem alguma utilidade geral, então aqui está:
Pode não ser a melhor solução, mas às vezes você pode usar shared_ptr . Se é claro que é um pouco exagerado, mas ... quanto ao unique_ptr, talvez eu espere mais 10 anos até que os fabricantes de padrões C ++ decidam usar o lambda como um deleter.
Outro lado. Pelo seu código, pode acontecer que, no estágio de destruição, window_impl esteja incompleto. Isso pode ser uma razão de comportamento indefinido. Veja o seguinte: por que, na verdade, excluir um tipo incompleto é um comportamento indefinido?
Então, se possível, eu definiria um objeto muito básico para todos os seus objetos, com destruidor virtual. E você é quase bom. Você deve ter em mente que o sistema chamará destruidor virtual para o seu ponteiro; portanto, você deve defini-lo para todos os ancestrais. Você também deve definir a classe base na seção de herança como virtual (consulte isso para obter detalhes).