Eu tenho uma hierarquia de classes para a qual gostaria de separar a interface da implementação. Minha solução é ter duas hierarquias: uma hierarquia de classes de identificador para a interface e uma hierarquia de classes não pública para a implementação. A classe de identificador base possui um ponteiro para implementação que as classes de identificador derivadas convertem em um ponteiro do tipo derivado (consulte a função getPimpl()
).
Aqui está um esboço da minha solução para uma classe base com duas classes derivadas. Existe uma solução melhor?
Arquivo "Base.h":
#include <memory>
class Base {
protected:
class Impl;
std::shared_ptr<Impl> pImpl;
Base(Impl* pImpl) : pImpl{pImpl} {};
...
};
class Derived_1 final : public Base {
protected:
class Impl;
inline Derived_1* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_1(...);
void func_1(...) const;
...
};
class Derived_2 final : public Base {
protected:
class Impl;
inline Derived_2* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_2(...);
void func_2(...) const;
...
};
Arquivo "Base.cpp":
class Base::Impl {
public:
Impl(...) {...}
...
};
class Derived_1::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_1(...) {...}
...
};
class Derived_2::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_2(...) {...}
...
};
Derived_1::Derived_1(...) : Base(new Derived_1::Impl(...)) {...}
Derived_1::func_1(...) const { getPimpl()->func_1(...); }
Derived_2::Derived_2(...) : Base(new Derived_2::Impl(...)) {...}
Derived_2::func_2(...) const { getPimpl()->func_2(...); }
Base
, uma classe base abstrata normal ("interface") e implementações concretas sem pimpl podem ser suficientes.Respostas:
Eu acho que é uma péssima estratégia para
Derived_1::Impl
derivarBase::Impl
.O principal objetivo do uso do idioma Pimpl é ocultar os detalhes de implementação de uma classe. Ao deixar
Derived_1::Impl
derivarBase::Impl
, você derrotou esse objetivo. Agora, não apenas a implementação deBase
dependeBase::Impl
, a implementação deDerived_1
também dependeBase::Impl
.Isso depende de quais trade-offs são aceitáveis para você.
Solução 1
Torne as
Impl
aulas totalmente independentes. Isso implica que haverá dois ponteiros para asImpl
classes - umBase
e outroDerived_N
.Solução 2
Exponha as classes apenas como alças. Não exponha as definições e implementações de classe.
Arquivo de cabeçalho público:
Aqui está uma implementação rápida
Prós e contras
Com a primeira abordagem, você pode construir
Derived
classes na pilha. Com a segunda abordagem, isso não é uma opção.Com a primeira abordagem, você incorre no custo de duas alocações e desalocações dinâmicas para construir e destruir um
Derived
na pilha. Se você construir e destruir umDerived
objeto da pilha, incorre no custo de mais uma alocação e desalocação. Com a segunda abordagem, você incorre apenas no custo de uma alocação dinâmica e uma desalocação para cada objeto.Com a primeira abordagem, você tem a capacidade de usar a
virtual
função membro isBase
. Com a segunda abordagem, isso não é uma opção.Minha sugestão
Eu usaria a primeira solução para poder usar a hierarquia de classes e as
virtual
funções de membroBase
, embora isso seja um pouco mais caro.fonte
A única melhoria que posso ver aqui é permitir que as classes concretas definam o campo de implementação. Se as classes base abstratas precisarem, elas podem definir uma propriedade abstrata que seja fácil de implementar nas classes concretas:
Base.h
Base.cpp
Isso parece ser mais seguro para mim. Se você tem uma árvore grande, também pode introduzir
virtual std::shared_ptr<Impl1> getImpl1() =0
no meio da árvore.fonte