J'ai une hiérarchie de classes pour laquelle je voudrais séparer l'interface de l'implémentation. Ma solution est d'avoir deux hiérarchies: une hiérarchie de classe de poignée pour l'interface et une hiérarchie de classe non publique pour l'implémentation. La classe de descripteurs de base a un pointeur vers l'implémentation que les classes de descripteurs dérivées convertissent en un pointeur du type dérivé (voir fonction getPimpl()
).
Voici un croquis de ma solution pour une classe de base avec deux classes dérivées. Y a-t-il une meilleure solution?
Fichier "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;
...
};
Fichier "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
, une classe de base abstraite normale ("interface") et des implémentations concrètes sans pimpl pourraient suffire.Réponses:
Je pense que c'est une mauvaise stratégie à faire
Derived_1::Impl
dériverBase::Impl
.L'objectif principal de l'utilisation de l'idiome Pimpl est de masquer les détails d'implémentation d'une classe. En laissant
Derived_1::Impl
dériverBase::Impl
, vous avez vaincu cet objectif. Maintenant, non seulement la mise en œuvre deBase
dépendBase::Impl
, la mise en œuvre deDerived_1
dépend également deBase::Impl
.Cela dépend des compromis que vous acceptez.
Solution 1
Rendez les
Impl
classes totalement indépendantes. Cela impliquera qu'il y aura deux pointeurs vers lesImpl
classes - un dansBase
et un autre dansDerived_N
.Solution 2
Exposez les classes uniquement en tant que descripteurs. N'exposez pas du tout les définitions de classe et les implémentations.
Fichier d'en-tête public:
Voici une mise en œuvre rapide
Avantages et inconvénients
Avec la première approche, vous pouvez construire des
Derived
classes dans la pile. Avec la deuxième approche, ce n'est pas une option.Avec la première approche, vous encourez le coût de deux allocations et désallocations dynamiques pour la construction et la destruction d'un
Derived
dans la pile. Si vous construisez et détruisez unDerived
objet à partir du tas vous, encourez le coût d'une allocation et d'une désallocation supplémentaires. Avec la deuxième approche, vous n'encourez que le coût d'une allocation dynamique et d'une désallocation pour chaque objet.Avec la première approche, vous avez la possibilité d'utiliser la
virtual
fonction membre estBase
. Avec la deuxième approche, ce n'est pas une option.Ma suggestion
J'irais avec la première solution pour pouvoir utiliser la hiérarchie des classes et
virtual
les fonctions membresBase
même si c'est un peu plus cher.la source
La seule amélioration que je peux voir ici est de laisser les classes concrètes définir le champ d'implémentation. Si les classes de base abstraites en ont besoin, elles peuvent définir une propriété abstraite facile à implémenter dans les classes concrètes:
Base.h
Base.cpp
Cela me semble plus sûr. Si vous avez un grand arbre, vous pouvez également l'introduire
virtual std::shared_ptr<Impl1> getImpl1() =0
au milieu de l'arbre.la source