Estou usando o pimpl-idiom com std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
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_ptr
deve poder ser usado com um tipo incompleto. Isso é um bug no libc ++ ou estou fazendo algo errado aqui?
Respostas:
Aqui estão alguns exemplos de
std::unique_ptr
tipos 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_ptr
não funcionará:já que o compilador deve saber aqui como destruir esse objeto de duração estática. Uma solução alternativa é:
fonte
foo::~foo() = default;
no arquivo srcComo Alexandre C. mencionou, o problema se resume ao
window
destruidor 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 .fonte
use um deleter personalizado
O problema é que
unique_ptr<T>
deve chamar o destruidorT::~T()
em seu próprio destruidor, seu operador de atribuição de movimentação eunique_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&&)
eunique_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
static
membrofoo
em conjunto com uma lambda:fonte
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:
fonte
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_ptr
de 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_ptr
com 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á:fonte
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).
fonte