Ich habe eine Klassenhierarchie, für die ich die Schnittstelle von der Implementierung trennen möchte. Meine Lösung besteht darin, zwei Hierarchien zu haben: eine Handle-Klassenhierarchie für die Schnittstelle und eine nicht öffentliche Klassenhierarchie für die Implementierung. Die Basis-Handle-Klasse verfügt über einen Zeiger auf die Implementierung, den die abgeleiteten Handle-Klassen in einen Zeiger des abgeleiteten Typs umwandeln (siehe Funktion getPimpl()
).
Hier ist eine Skizze meiner Lösung für eine Basisklasse mit zwei abgeleiteten Klassen. Gibt es eine bessere Lösung?
Datei "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;
...
};
Datei "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
, könnten eine normale abstrakte Basisklasse ("Schnittstelle") und konkrete Implementierungen ohne Pimpl ausreichen.Antworten:
Ich denke, es ist eine schlechte Strategie,
Derived_1::Impl
daraus abzuleitenBase::Impl
.Der Hauptzweck der Verwendung des Pimpl-Idioms besteht darin, die Implementierungsdetails einer Klasse auszublenden. Indem Sie
Derived_1::Impl
ableiten lassenBase::Impl
, haben Sie diesen Zweck besiegt. Nun, nicht nur , dass die UmsetzungBase
hängt davon abBase::Impl
, die Umsetzung derDerived_1
auch abhängig vonBase::Impl
.Das hängt davon ab, welche Kompromisse für Sie akzeptabel sind.
Lösung 1
Machen Sie den
Impl
Unterricht völlig unabhängig. Dies bedeutet, dass es zwei Zeiger aufImpl
Klassen gibt - einen inBase
und einen inDerived_N
.Lösung 2
Stellen Sie die Klassen nur als Handles zur Verfügung. Machen Sie die Klassendefinitionen und Implementierungen überhaupt nicht verfügbar.
Öffentliche Header-Datei:
Hier ist eine schnelle Implementierung
Vor-und Nachteile
Mit dem ersten Ansatz können Sie
Derived
Klassen im Stapel erstellen. Beim zweiten Ansatz ist dies keine Option.Beim ersten Ansatz fallen die Kosten für zwei dynamische Zuweisungen und Freigaben für die Erstellung und Zerstörung eines
Derived
Stacks an. Wenn Sie einDerived
Objekt aus dem Heap erstellen und zerstören , entstehen Ihnen die Kosten für eine weitere Zuordnung und Freigabe. Beim zweiten Ansatz fallen nur die Kosten für eine dynamische Zuordnung und eine Freigabe für jedes Objekt an.Mit dem ersten Ansatz erhalten Sie die Möglichkeit, die
virtual
Mitgliedsfunktion zu nutzenBase
. Beim zweiten Ansatz ist dies keine Option.Mein Vorschlag
Ich würde mich für die erste Lösung entscheiden, damit ich die Klassenhierarchie und die
virtual
Elementfunktionen verwenden kannBase
, obwohl sie etwas teurer sind.quelle
Die einzige Verbesserung, die ich hier sehen kann, besteht darin, dass die konkreten Klassen das Implementierungsfeld definieren. Wenn die abstrakten Basisklassen dies benötigen, können sie eine abstrakte Eigenschaft definieren, die in den konkreten Klassen einfach zu implementieren ist:
Base.h
Base.cpp
Das scheint mir sicherer zu sein. Wenn Sie einen großen Baum haben, können Sie ihn auch
virtual std::shared_ptr<Impl1> getImpl1() =0
in der Mitte des Baumes einführen .quelle