Pergunta

Essa pergunta já tem resposta aqui:

Fundo:

O Idioma PIMPL (Pointer to IMPLementation) é uma técnica de ocultação de implementação na qual uma classe pública envolve uma estrutura ou classe que não pode ser vista fora da biblioteca da qual a classe pública faz parte.

Isso oculta detalhes e dados internos de implementação do usuário da biblioteca.

Ao implementar esse idioma, por que você colocaria os métodos públicos na classe pimpl e não na classe pública, já que as implementações dos métodos das classes públicas seriam compiladas na biblioteca e o usuário teria apenas o arquivo de cabeçalho?

Para ilustrar, este código coloca o Purr() implementação na classe impl e a envolve também.

Por que não implementar Purr diretamente na classe pública?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}
Foi útil?

Solução

  • Porque você quer Purr() para poder usar membros privados de CatImpl. Cat::Purr() não seria permitido tal acesso sem um friend declaração.
  • Porque você não mistura responsabilidades:uma classe implementa, uma classe encaminha.

Outras dicas

Acho que a maioria das pessoas se refere a isso como a expressão Handle Body.Consulte o livro Advanced C++ Programming Styles and Idioms de James Coplien (Link da Amazon).Também é conhecido como Gato de Cheshire por causa do personagem de Lewis Caroll que desaparece até restar apenas o sorriso.

O código de exemplo deve ser distribuído em dois conjuntos de arquivos de origem.Então apenas Cat.h é o arquivo enviado com o produto.

CatImpl.h é incluído por Cat.cpp e CatImpl.cpp contém a implementação de CatImpl::Purr().Isso não ficará visível para o público que usa seu produto.

Basicamente, a ideia é esconder o máximo possível a implementação de olhares indiscretos.Isso é mais útil quando você tem um produto comercial enviado como uma série de bibliotecas acessadas por meio de uma API na qual o código do cliente é compilado e vinculado.

Fizemos isso com a reescrita do produto IONA Orbix 3.3 em 2000.

Conforme mencionado por outros, o uso de sua técnica desacopla completamente a implementação da interface do objeto.Então você não precisará recompilar tudo que usa Cat se quiser apenas alterar a implementação de Purr().

Esta técnica é usada em uma metodologia chamada projeto por contrato.

Pelo que vale, separa a implementação da interface.Isso geralmente não é muito importante em projetos de pequeno porte.Mas, em grandes projetos e bibliotecas, pode ser usado para reduzir significativamente o tempo de construção.

Considere que a implementação de Cat pode incluir muitos cabeçalhos, pode envolver metaprogramação de modelo que leva tempo para ser compilada por conta própria.Por que um usuário, que deseja apenas usar o Cat tem que incluir tudo isso?Conseqüentemente, todos os arquivos necessários são ocultados usando o idioma pimpl (daí a declaração direta de CatImpl), e o uso da interface não força o usuário a incluí-los.

Estou desenvolvendo uma biblioteca para otimização não linear (leia "muita matemática desagradável"), que é implementada em modelos, portanto a maior parte do código está em cabeçalhos.Demora cerca de cinco minutos para compilar (em uma CPU multi-core decente) e apenas analisar os cabeçalhos em um arquivo vazio .cpp leva cerca de um minuto.Portanto, qualquer pessoa que use a biblioteca precisa esperar alguns minutos toda vez que compila seu código, o que torna o desenvolvimento bastante tedioso.Porém, ao ocultar a implementação e os cabeçalhos, inclui-se apenas um arquivo de interface simples, que é compilado instantaneamente.

Não tem necessariamente nada a ver com proteger a implementação de ser copiada por outras empresas - o que provavelmente não aconteceria de qualquer maneira, a menos que o funcionamento interno do seu algoritmo possa ser adivinhado a partir das definições das variáveis-membro (se assim for, é provavelmente não é muito complicado e nem vale a pena proteger em primeiro lugar).

Se sua classe usa o idioma pimpl, você pode evitar alterar o arquivo de cabeçalho na classe pública.

Isso permite adicionar/remover métodos à classe pimpl, sem modificar o arquivo de cabeçalho da classe externa.Você também pode adicionar/remover #includes ao pimpl.

Ao alterar o arquivo de cabeçalho da classe externa, você deve recompilar tudo o que #inclui (e se algum deles for arquivo de cabeçalho, você deve recompilar tudo o que #inclui e assim por diante)

Normalmente, a única referência à classe Pimpl no cabeçalho da classe Owner (Cat neste caso) seria uma declaração direta, como você fez aqui, porque isso pode reduzir bastante as dependências.

Por exemplo, se sua classe Pimpl tiver ComplicatedClass como membro (e não apenas um ponteiro ou referência a ela), você precisará ter ComplicatedClass totalmente definido antes de usá-lo.Na prática, isso significa incluir "ComplicatedClass.h" (que também incluirá indiretamente qualquer coisa da qual ComplicatedClass dependa).Isso pode fazer com que um único preenchimento de cabeçalho atraia muitas coisas, o que é ruim para gerenciar suas dependências (e seus tempos de compilação).

Ao usar o pimpl idion, você só precisa #include o material usado na interface pública do seu tipo Owner (que seria Cat aqui).O que torna as coisas melhores para as pessoas que usam sua biblioteca e significa que você não precisa se preocupar com pessoas que dependem de alguma parte interna de sua biblioteca - seja por engano ou porque querem fazer algo que você não permite, então elas #define público privado antes de incluir seus arquivos.

Se for uma classe simples, geralmente não há razão para usar um Pimpl, mas para momentos em que os tipos são muito grandes, pode ser uma grande ajuda (especialmente para evitar longos tempos de construção)

Bem, eu não usaria.Eu tenho uma alternativa melhor:

foo.h:

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

foo.cpp:

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() { 
            //....
        }     
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

Esse padrão tem nome?

Como também programador Python e Java, gosto muito mais disso do que do idioma pImpl.

Usamos a linguagem PIMPL para emular programação orientada a aspectos onde aspectos pré, pós e erro são chamados antes e depois da execução de uma função membro.

struct Omg{
   void purr(){ cout<< "purr\n"; }
};

struct Lol{
  Omg* omg;
  /*...*/
  void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } }
};

Também usamos o ponteiro para a classe base para compartilhar diferentes aspectos entre muitas classes.

A desvantagem desta abordagem é que o usuário da biblioteca tem que levar em conta todos os aspectos que serão executados, mas apenas vê sua classe.Requer consultar a documentação para detectar quaisquer efeitos colaterais.

Colocar a chamada para impl->Purr dentro do arquivo cpp significa que no futuro você poderá fazer algo completamente diferente sem precisar alterar o arquivo de cabeçalho.Talvez no próximo ano eles descubram um método auxiliar que poderiam ter chamado e assim possam alterar o código para chamá-lo diretamente e não usar impl->Purr.(Sim, eles poderiam conseguir a mesma coisa atualizando o método impl::Purr real também, mas nesse caso você está preso com uma chamada de função extra que não alcança nada além de chamar a próxima função por sua vez)

Isso também significa que o cabeçalho possui apenas definições e não possui nenhuma implementação que proporcione uma separação mais limpa, que é o objetivo do idioma.

Acabei de implementar minha primeira aula de espinhas nos últimos dias.Usei-o para eliminar problemas que estava tendo ao incluir o Winsock2.h no Borland Builder.Parecia estar atrapalhando o alinhamento da estrutura e como eu tinha coisas de soquete nos dados privados da classe, esses problemas estavam se espalhando para qualquer arquivo cpp que incluísse o cabeçalho.

Ao usar o pimpl, o winsock2.h foi incluído em apenas um arquivo cpp onde eu poderia resolver o problema e não me preocupar se ele voltaria para me morder.

Para responder à pergunta original, a vantagem que encontrei em encaminhar as chamadas para a classe pimpl foi que a classe pimpl é a mesma que sua classe original seria antes de você fazer o pimpl, além de suas implementações não estarem espalhadas por 2 aulas de uma forma estranha.É muito mais claro implementar os públicos para simplesmente encaminhar para a classe pimpl.

Como disse o Sr. Nodet, uma classe, uma responsabilidade.

Não sei se essa é uma diferença que vale a pena mencionar, mas...

Seria possível ter a implementação em seu próprio namespace e ter um namespace público de wrapper/biblioteca para o código que o usuário vê:

catlib::Cat::Purr(){ cat_->Purr(); }
cat::Cat::Purr(){
   printf("purrrrrr");
}

Desta forma, todo o código da biblioteca pode fazer uso do namespace cat e conforme surgir a necessidade de expor uma classe ao usuário, um wrapper pode ser criado no namespace catlib.

Acho revelador que, apesar de quão bem conhecida seja a expressão pimpl, não a vejo surgir com muita frequência na vida real (por exemplo,em projetos de código aberto).

Muitas vezes me pergunto se os “benefícios” são exagerados;sim, você pode deixar alguns detalhes de sua implementação ainda mais ocultos e, sim, você pode alterar sua implementação sem alterar o cabeçalho, mas não é óbvio que essas sejam grandes vantagens na realidade.

Ou seja, não está claro se há necessidade de sua implementação ser que bem escondido, e talvez seja muito raro que as pessoas realmente mudem apenas a implementação;assim que você precisar adicionar novos métodos, digamos, você precisará alterar o cabeçalho de qualquer maneira.

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