Qual é o sentido do padrão PImpl enquanto podemos usar a interface para o mesmo propósito em C ++?

45

Eu vejo muito código-fonte que usa o idioma do PImpl em C ++. Eu suponho que seu objetivo é ocultar os dados privados / tipo / implementação, para que ele possa remover a dependência e, em seguida, reduzir o tempo de compilação e o problema de inclusão do cabeçalho.

Mas as classes interface / pure-abstract em C ++ também possuem esse recurso, elas também podem ser usadas para ocultar dados / tipo / implementação. E para permitir que o chamador veja apenas a interface ao criar um objeto, podemos declarar um método de fábrica no cabeçalho da interface.

A comparação é:

  1. Custo :

    O custo do modo de interface é menor, porque você nem precisa repetir a implementação da função de invólucro público void Bar::doWork() { return m_impl->doWork(); } , basta definir a assinatura na interface.

  2. Bem entendido :

    A tecnologia de interface é melhor compreendida por todos os desenvolvedores de C ++.

  3. Desempenho :

    O desempenho da interface não é pior do que o idioma do PImpl, tanto um acesso extra à memória. Eu assumo que o desempenho é o mesmo.

A seguir, o pseudocódigo para ilustrar minha pergunta:

// Forward declaration can help you avoid include BarImpl header, and those included in BarImpl header.
class BarImpl;
class Bar
{
public:
    // public functions
    void doWork();
private:
    // You don't need to compile Bar.cpp after changing the implementation in BarImpl.cpp
    BarImpl* m_impl;
};

O mesmo propósito pode ser implementado usando a interface:

// Bar.h
class IBar
{
public:
    virtual ~IBar(){}
    // public functions
    virtual void doWork() = 0;
};

// to only expose the interface instead of class name to caller
IBar* createObject();

Então, qual é o objetivo do PImpl?

    
por ZijingWu 03.10.2013 / 13:00
fonte

3 respostas

43

Primeiro, o PImpl é geralmente usado para classes não polimórficas. E quando uma classe polimórfica tem PImpl, ela geralmente permanece polimórfica, isto é, ainda implementa interfaces e substitui os métodos virtuais da classe base e assim por diante. Portanto, a implementação mais simples do PImpl é a interface não , é uma classe simples contendo diretamente os membros!

Existem três razões para usar o PImpl:

  1. Fazendo a interface binária (ABI) independente dos membros privados. É possível atualizar uma biblioteca compartilhada sem recompilar o código dependente, mas somente enquanto a interface binário permanecer a mesma. Agora quase qualquer alteração no cabeçalho, exceto para adicionar uma função não membro e adicionar uma função de membro não virtual, altera a ABI. O idioma do PImpl move a definição dos membros privados para a fonte e, assim, dissocia a ABI de sua definição. Veja Problema da interface binária frágil

  2. Quando um cabeçalho é alterado, todas as fontes, inclusive ele, precisam ser recompiladas. E compilação C ++ é bastante lenta. Então, movendo as definições dos membros privados para a fonte, o idioma do PImpl reduz o tempo de compilação, pois menos dependências precisam ser puxadas no cabeçalho e reduz o tempo de compilação após as modificações, já que os dependentes não precisam ser recompilados. (ok, isso também se aplica à função interface + factory com classes de concreto ocultas).

  3. Para muitas classes em exceção de C ++, a segurança é uma propriedade importante. Muitas vezes você precisa compor várias classes em uma para que, se durante a operação em mais de um membro for lançado, nenhum dos membros seja modificado ou você tenha operações que deixarão o membro em estado inconsistente se ele for acionado e você precisar que o objeto contido permaneça consistente. Nesse caso, você implementa a operação criando uma nova instância do PImpl e as troca quando a operação é bem-sucedida.

Na verdade, a interface também pode ser usada apenas para fins de implementação, mas tem as seguintes desvantagens:

  1. Adicionar método não virtual não quebra a ABI, mas adiciona uma virtual. As interfaces, portanto, não permitem a adição de métodos, o PImpl faz.

  2. A interface só pode ser usada por meio de ponteiro / referência, portanto, o usuário deve cuidar do gerenciamento adequado de recursos. Por outro lado, as classes que usam o PImpl ainda são tipos de valor e manipulam os recursos internamente.

  3. A implementação oculta não pode ser herdada, a classe com o PImpl pode.

E, claro, a interface não ajudará na segurança de exceção. Você precisa da indireção dentro da classe para isso.

    
por 03.10.2013 / 13:17
fonte
6

Eu só quero abordar seu ponto de desempenho. Se você usa uma interface, você necessariamente criou funções virtuais que NÃO serão embutidas pelo otimizador do compilador. As funções PIMPL podem (e provavelmente serão, porque são tão curtas) serem embutidas (talvez mais de uma vez se a função IMPL também for pequena). Uma chamada de função virtual não pode ser otimizada, a menos que você use otimizações de análise de programa completas que levam muito tempo para serem executadas.

Se a sua classe PIMPL não é usada de uma forma crítica para o desempenho, então este ponto não importa muito, mas sua suposição de que o desempenho é o mesmo só vale em algumas situações, não em todas.

    
por 24.10.2013 / 00:06
fonte
5

Esta página responde à sua pergunta. Muitas pessoas concordam com sua afirmação. Usar o Pimpl tem as seguintes vantagens sobre campos / membros privados.

  1. Changing private member variables of a class does not require recompiling classes that depend on it, thus make times are faster, and the FragileBinaryInterfaceProblem is reduced.
  2. The header file does not need to #include classes that are used 'by value' in private member variables, thus compile times are faster.

O idioma Pimpl é uma otimização em tempo de compilação, uma técnica de quebra de dependência. Ele reduz arquivos de cabeçalho grandes. Reduz o seu cabeçalho para apenas a interface pública. O comprimento do cabeçalho é importante no C ++ quando você pensa sobre como ele funciona. #include efetivamente concatena o arquivo de cabeçalho com o arquivo de origem - assim, tenha em mente que as unidades C ++ pré-processadas podem ser muito grandes. Usar o Pimpl pode melhorar os tempos de compilação.

Existem outras técnicas melhores de quebra de dependência - que envolvem a melhoria do seu design. O que os métodos privados representam? Qual é a responsabilidade única? Devem ser outra aula?

    
por 03.10.2013 / 13:16
fonte