Pergunta

Eu queria saber o que iria fazer um programador para escolher Pimpl idioma ou classe virtual pura e herança.

Eu entendo que pimpl idioma vem com um engano adicional explícita para cada método público ea sobrecarga de criação do objeto.

A classe virtual pura no outro lado vem com engano implícita (vtable) para a implementação de herdar e eu entendo que nenhuma sobrecarga de criação do objeto.
Editar : Mas você precisaria de uma fábrica se você criar o objeto do lado de fora

O que faz a classe virtual pura menos desejável do que o pimpl idioma?

Foi útil?

Solução

Ao escrever uma classe C ++, é apropriado pensar sobre se ele vai ser

  1. Um tipo de valor

    Copiar por valor, identidade nunca é importante. É apropriado para que seja uma chave em uma std :: map. Exemplo, uma classe "string" ou uma classe "data", ou uma classe "número complexo". Para "copiar" exemplos dessa classe uma faz sentido.

  2. Tipo Uma Entidade

    A identidade é importante. Sempre passados ??por referência, não pelo "valor". Muitas vezes, não faz sentido a instâncias "copiar" da classe em tudo. Quando se faz sentido, um método polimórfico "Clone" é geralmente mais apropriado. Exemplos: A classe Socket, uma classe de banco de dados, uma classe "política", tudo o que seria um "fechamento" em uma linguagem funcional

  3. .

Ambos Pimpl e classe base abstrata pura são técnicas para reduzir dependências de tempo de compilação.

No entanto, eu só usar Pimpl para implementar tipos Valor (tipo 1), e só às vezes, quando eu realmente quero minimizar o acoplamento e dependências de tempo de compilação. Muitas vezes, não vale a pena. Como você observam com razão, não há sobrecarga mais sintática, porque você tem que escrever métodos de encaminhamento para todos os métodos públicos. Para o tipo 2 aulas, eu sempre usar uma classe base abstrata pura com método de fábrica associado (s).

Outras dicas

Pointer to implementation é geralmente sobre como ocultar detalhes estruturais de implementação. Interfaces está prestes instanciação diferentes implementações. Eles realmente servir a dois propósitos diferentes.

O idioma pimpl ajuda a reduzir dependências de compilação e tempos especialmente em aplicações de grande porte, ea exposição minimiza cabeçalho dos detalhes de implementação de sua classe a uma unidade de compilação. Os usuários de sua classe não deveria sequer precisa estar ciente da existência de uma espinha (exceto como um ponteiro enigmática para que eles não estão a par!).

aulas abstratas (virtuais puras) é algo de 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 permitindo-lhes criar seus objetos (por exemplo, através de métodos de fábrica ou classes, injecção dependência ou outros mecanismos).

Eu estava procurando uma resposta para a mesma pergunta. Depois de ler alguns artigos e algumas práticas Eu prefiro usar "interfaces de classe virtual pura" .

  1. Eles são mais para a frente (esta é uma opinião subjectiva). Pimpl idioma me faz sentir que estou escrevendo código "para o compilador", não para a "próxima desenvolvedor" que vai ler o meu código.
  2. Algumas estruturas de teste tem suporte direto para zombaria aulas virtuais puros
  3. É verdade que você necessidade uma fábrica para ser acessível a partir do exterior. Mas se você quiser polimorfismo alavancagem: que também é "pro", não um "con". ... e um método simples fábrica realmente não dói tanto

A única desvantagem ( Eu estou tentando investigar sobre este ) é que pimpl idioma poderia ser mais rápido

  1. Quando o proxy-chamadas são inline, herdando necessariamente precisa de um acesso extra para o objeto VTABLE em tempo de execução
  2. o consumo de memória do pimpl público-proxy-classe é menor (você pode fazer facilmente otimizações para swaps mais rápidos e outras otimizações semelhantes)

Há um problema muito real com bibliotecas compartilhadas que os contorna pimpl idiom perfeitamente que virtuals puros podem não: você não pode modificar com segurança / membros Remover dados de uma classe sem forçar os usuários da classe de recompilar seu código. Isso pode ser aceitável em algumas circunstâncias, mas não, por exemplo, para bibliotecas do sistema.

Para explicar o problema em detalhes, considere o seguinte código na sua biblioteca compartilhada / cabeçalho:

// header
struct A
{
public:
  A();
  // more public interface, some of which uses the int below
private:
  int a;
};

// library 
A::A()
  : a(0)
{}

O código emite compilador na biblioteca compartilhada que calcula o endereço do inteiro para ser inicializado para ser um certo deslocamento (provavelmente zero neste caso, porque é o único membro) do ponteiro para o objeto A que sabe ser this.

No lado do usuário do código, uma new A iremos primeiro alocar sizeof(A) bytes de memória, em seguida, entregar um ponteiro para a memória para o construtor A::A() como this.

Em caso de uma revisão posterior de sua biblioteca que você decidir abandonar o inteiro, torná-lo maior, menor, ou adicionar membros, vai haver uma incompatibilidade entre a quantidade de aloca código do usuário memória, e os deslocamentos dos espera código construtor . O resultado provável é uma falha, se você tiver sorte -. Se você é menos sorte, seus comporta software estranhamente

Por pimpl'ing, você pode adicionar com segurança e membros de dados Remover para a classe interna, como a alocação de memória e chamada do construtor acontecer na biblioteca compartilhada:

// header
struct A
{
public:
  A();
  // more public interface, all of which delegates to the impl
private:
  void * impl;
};

// library 
A::A()
  : impl(new A_impl())
{}

Tudo que você precisa fazer agora é manter sua interface pública livre de outros que o ponteiro para o objeto de implementação membros de dados, e você está a salvo de esta classe de erros.

Editar: eu deveria talvez acrescentar que a única razão pela qual eu estou falando sobre o construtor aqui é que eu não quis fornecer mais código - a mesma argumentação aplica-se a todas as funções que os dados de acesso membros.

Eu odeio espinhas! Eles fazem a classe feio e não legível. Todos os métodos são redirecionados para borbulha. Você nunca vê nos cabeçalhos, o que funcionalidades tem a classe, então você não pode refatorar-lo (e. G., Simplesmente alterar a visibilidade de um método). A classe se sente como "grávida". Acho que usando iterfaces é melhor e realmente o suficiente para esconder a implementação do cliente. Você pode evento deixe uma classe implementar várias interfaces para mantê-los fino. Deve-se preferir as interfaces! Nota: Você precisa não é necessário a classe de fábrica. Relevante é que os clientes de classe comunicar com instâncias de TI por meio da interface apropriada. O esconderijo de métodos privados Acho como paranóia estranho e não vejo razão para isso, uma vez que hav interfaces.

Não devemos esquecer que a herança é um acoplamento mais forte, mais perto do que delegação. Também gostaria de ter em conta todas as questões levantadas nas respostas dadas ao decidir que idiomas de design para empregar em resolver um problema particular.

Embora amplamente coberto em outras respostas talvez eu possa ser um pouco mais explícito sobre um benefício de pimpl sobre classes base virtuais:

Uma abordagem pimpl é transparente do ponto de vista do usuário, o que significa que você pode e criar objetos da classe na pilha e usá-los diretamente em recipientes. Se você tentar esconder a implementação usando uma classe base virtual abstrato, você precisará retornar um ponteiro compartilhada para a classe base de uma fábrica, o que complica a sua utilização. Considere o seguinte código de cliente equivalentes:

// Pimpl
Object pi_obj(10);
std::cout << pi_obj.SomeFun1();

std::vector<Object> objs;
objs.emplace_back(3);
objs.emplace_back(4);
objs.emplace_back(5);
for (auto& o : objs)
    std::cout << o.SomeFun1();

// Abstract Base Class
auto abc_obj = ObjectABC::CreateObject(20);
std::cout << abc_obj->SomeFun1();

std::vector<std::shared_ptr<ObjectABC>> objs2;
objs2.push_back(ObjectABC::CreateObject(13));
objs2.push_back(ObjectABC::CreateObject(14));
objs2.push_back(ObjectABC::CreateObject(15));
for (auto& o : objs2)
    std::cout << o->SomeFun1();

No meu entendimento essas duas coisas servem a propósitos completamente diferentes. O objetivo do idioma espinha é, basicamente, dar-lhe um identificador para a sua implementação de modo que você pode fazer coisas como swaps rápidas para uma espécie.

O objetivo das aulas virtuais é mais ao longo da linha de permitir que o polimorfismo, ou seja, você tem um ponteiro desconhecido para um objeto de um tipo derivado e quando você chamar a função x você sempre terá a função certa para qualquer classe o ponteiro de base, na verdade pontos a.

As maçãs e as laranjas realmente.

O problema mais irritante sobre o idioma pimpl é que faz com que seja extremamente difícil de manter e analisar o código existente. Então, usando pimpl você paga com o tempo desenvolvedor e frustração apenas para "reduzir dependências de compilação e horários e minimizar a exposição cabeçalho dos detalhes de implementação". Decida-se, se é realmente vale a pena.

Especialmente "tempos de construção" é um problema que você pode resolver através de uma melhor hardware ou usando ferramentas como IncrediBuild (www.incredibuild.com, também já incluídos no Visual Studio 2017), não afectando assim o seu design de software. design de software devem ser geralmente independente da forma como o software é construído.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top