Eu estava me perguntando o que faria um programador escolher o idioma Pimpl ou classe virtual pura e herança.
Eu entendo que o idioma pimpl vem com uma indireção extra explícita para cada método público e a sobrecarga de criação de objeto.
A classe virtual Pure, por outro lado, vem com indireção implícita (vtable) para a implementação de herança e eu entendo que nenhuma sobrecarga de criação de objeto.
EDIT : Mas você precisaria de uma fábrica se criar o objeto de fora
O que torna a classe virtual pura menos desejável do que o idioma pimpl?
c++
abstract-class
pimpl-idiom
Arkaitz Jimenez
fonte
fonte
Respostas:
Ao escrever uma classe C ++, é apropriado pensar se ela será
Um tipo de valor
Cópia por valor, a identidade nunca é importante. É apropriado que seja uma chave em um std :: map. Por exemplo, uma classe de "string" ou uma classe de "data" ou uma classe de "número complexo". Faz sentido "copiar" instâncias de tal classe.
Um tipo de entidade
A identidade é importante. Sempre passado por referência, nunca por "valor". Freqüentemente, não faz sentido "copiar" as instâncias da classe. Quando faz sentido, um método polimórfico de "clone" geralmente é mais apropriado. Exemplos: uma classe Socket, uma classe Database, uma classe "política", qualquer coisa que seria um "fechamento" em uma linguagem funcional.
Tanto pImpl quanto a classe base abstrata pura são técnicas para reduzir as dependências do tempo de compilação.
No entanto, eu sempre uso pImpl para implementar tipos de valor (tipo 1) e apenas às vezes quando realmente quero minimizar o acoplamento e as dependências de tempo de compilação. Freqüentemente, não vale a pena se preocupar. Como você aponta corretamente, há mais sobrecarga sintática porque você precisa escrever métodos de encaminhamento para todos os métodos públicos. Para classes do tipo 2, sempre uso uma classe base abstrata pura com método (s) de fábrica associado (s).
fonte
Pointer to implementation
é geralmente sobre como ocultar detalhes de implementação estrutural.Interfaces
são sobre instanciar implementações diferentes. Eles realmente servem a dois propósitos diferentes.fonte
O idioma pimpl ajuda a reduzir as dependências e os tempos de construção, especialmente em aplicativos grandes, e minimiza a exposição do cabeçalho dos detalhes de implementação de sua classe em uma unidade de compilação. Os usuários de sua classe nem precisam estar cientes da existência de uma espinha (exceto como um indicador enigmático do qual eles não têm conhecimento!).
Classes abstratas (virtuais puros) é algo que seus clientes devem estar cientes: se você tentar usá-los para reduzir o acoplamento e referências circulares, você precisa adicionar alguma forma de permitir que eles criem seus objetos (por exemplo, através de métodos de fábrica ou classes, injeção de dependência ou outros mecanismos).
fonte
Eu estava procurando uma resposta para a mesma pergunta. Depois de ler alguns artigos e praticar , prefiro usar "interfaces de classes virtuais puras" .
A única desvantagem ( estou tentando investigar sobre isso ) é que o idioma pimpl pode ser mais rápido
fonte
Eu odeio espinhas! Eles fazem as aulas feias e ilegíveis. Todos os métodos são redirecionados para pimple. Você nunca vê nos cabeçalhos quais funcionalidades a classe possui, então você não pode refatorá-la (por exemplo, simplesmente alterar a visibilidade de um método). A aula parece "grávida". Acho que usar iterfaces é melhor e realmente o suficiente para ocultar a implementação do cliente. Você pode eventualmente permitir que uma classe implemente várias interfaces para mantê-los finos. Deve-se preferir interfaces! Nota: Você não precisa da classe de fábrica. O relevante é que os clientes da classe se comuniquem com suas instâncias por meio da interface apropriada. Considero a ocultação de métodos privados uma estranha paranóia e não vejo razão para isso, uma vez que temos interfaces.
fonte
Há um problema muito real com as bibliotecas compartilhadas que o idioma pimpl contorna perfeitamente que os virtuais puros não: você não pode modificar / remover membros de dados de uma classe com segurança sem forçar os usuários da classe a recompilar seu código. Isso pode ser aceitável em algumas circunstâncias, mas não para bibliotecas de sistema, por exemplo.
Para explicar o problema em detalhes, considere o seguinte código em sua biblioteca / cabeçalho compartilhado:
O compilador emite código na biblioteca compartilhada que calcula o endereço do inteiro a ser inicializado com um certo deslocamento (provavelmente zero neste caso, porque é o único membro) do ponteiro para o objeto A que ele sabe ser
this
.No lado do usuário do código, um
new A
primeiro alocarásizeof(A)
bytes de memória e, em seguida, passará um ponteiro para essa memória aoA::A()
construtor comothis
.Se em uma revisão posterior de sua biblioteca você decidir descartar o inteiro, torná-lo maior, menor ou adicionar membros, haverá uma incompatibilidade entre a quantidade de memória alocada pelo código do usuário e os deslocamentos que o código do construtor espera. O resultado provável é um travamento, se você tiver sorte - se você tiver menos sorte, seu software se comporta de maneira estranha.
Ao pimpl'ing, você pode adicionar e remover membros de dados da classe interna com segurança, conforme a alocação de memória e a chamada do construtor acontecem na biblioteca compartilhada:
Tudo o que você precisa fazer agora é manter sua interface pública livre de membros de dados que não sejam o ponteiro para o objeto de implementação, e você estará protegido contra essa classe de erros.
Edit: talvez eu deva acrescentar que a única razão pela qual estou falando sobre o construtor aqui é que eu não queria fornecer mais código - a mesma argumentação se aplica a todas as funções que acessam membros de dados.
fonte
class A_impl *impl_;
Não devemos esquecer que a herança é um acoplamento mais forte e mais próximo do que a delegação. Eu também levaria em consideração todas as questões levantadas nas respostas dadas ao decidir quais expressões de design usar para resolver um problema específico.
fonte
Embora amplamente coberto nas outras respostas, talvez eu possa ser um pouco mais explícito sobre um dos benefícios do pimpl sobre as classes base virtuais:
Uma abordagem pimpl é transparente do ponto de vista do usuário, o que significa que você pode, por exemplo, criar objetos da classe na pilha e usá-los diretamente em contêineres. Se você tentar ocultar a implementação usando uma classe base virtual abstrata, você precisará retornar um ponteiro compartilhado para a classe base de uma fábrica, complicando seu uso. Considere o seguinte código de cliente equivalente:
fonte
No meu entendimento, essas duas coisas servem a propósitos completamente diferentes. O objetivo do idioma pimple é basicamente fornecer um controle para sua implementação para que você possa fazer coisas como trocas rápidas por uma classificação.
O propósito das classes virtuais é mais na linha de permitir o polimorfismo, ou seja, você tem um ponteiro desconhecido para um objeto de um tipo derivado e quando você chama a função x você sempre obtém a função certa para qualquer classe para a qual o ponteiro base realmente aponta.
Maçãs e laranjas, na verdade.
fonte
O problema mais irritante sobre o idioma pimpl é que torna extremamente difícil manter e analisar o código existente. Portanto, usando o pimpl, você paga com o tempo e a frustração do desenvolvedor apenas para "reduzir as dependências e os tempos de construção e minimizar a exposição do cabeçalho dos detalhes de implementação". Decida você mesmo, se realmente vale a pena.
Especialmente os "tempos de construção" é um problema que você pode resolver com um hardware melhor ou usando ferramentas como o Incredibuild (www.incredibuild.com, também já incluído no Visual Studio 2017), não afetando o design do seu software. O design do software deve ser geralmente independente da forma como o software é construído.
fonte