Ich denke, es ist eine schlechte Strategie, Derived_1::Impl
daraus abzuleiten Base::Impl
.
Der Hauptzweck der Verwendung des Pimpl-Idioms besteht darin, die Implementierungsdetails einer Klasse auszublenden. Indem Sie Derived_1::Impl
ableiten lassen Base::Impl
, haben Sie diesen Zweck besiegt. Nun, nicht nur , dass die Umsetzung Base
hängt davon ab Base::Impl
, die Umsetzung der Derived_1
auch abhängig von Base::Impl
.
Gibt es eine bessere Lösung?
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 auf Impl
Klassen gibt - einen in Base
und einen in Derived_N
.
class Base {
protected:
Base() : pImpl{new Impl()} {}
private:
// It's own Impl class and pointer.
class Impl { };
std::shared_ptr<Impl> pImpl;
};
class Derived_1 final : public Base {
public:
Derived_1() : Base(), pImpl{new Impl()} {}
void func_1() const;
private:
// It's own Impl class and pointer.
class Impl { };
std::shared_ptr<Impl> pImpl;
};
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:
struct Handle {unsigned long id;};
struct Derived1_tag {};
struct Derived2_tag {};
Handle constructObject(Derived1_tag tag);
Handle constructObject(Derived2_tag tag);
void deleteObject(Handle h);
void fun(Handle h, Derived1_tag tag);
void bar(Handle h, Derived2_tag tag);
Hier ist eine schnelle Implementierung
#include <map>
class Base
{
public:
virtual ~Base() {}
};
class Derived1 : public Base
{
};
class Derived2 : public Base
{
};
namespace Base_Impl
{
struct CompareHandle
{
bool operator()(Handle h1, Handle h2) const
{
return (h1.id < h2.id);
}
};
using ObjectMap = std::map<Handle, Base*, CompareHandle>;
ObjectMap& getObjectMap()
{
static ObjectMap theMap;
return theMap;
}
unsigned long getNextID()
{
static unsigned id = 0;
return ++id;
}
Handle getHandle(Base* obj)
{
auto id = getNextID();
Handle h{id};
getObjectMap()[h] = obj;
return h;
}
Base* getObject(Handle h)
{
return getObjectMap()[h];
}
template <typename Der>
Der* getObject(Handle h)
{
return dynamic_cast<Der*>(getObject(h));
}
};
using namespace Base_Impl;
Handle constructObject(Derived1_tag tag)
{
// Construct an object of type Derived1
Derived1* obj = new Derived1;
// Get a handle to the object and return it.
return getHandle(obj);
}
Handle constructObject(Derived2_tag tag)
{
// Construct an object of type Derived2
Derived2* obj = new Derived2;
// Get a handle to the object and return it.
return getHandle(obj);
}
void deleteObject(Handle h)
{
// Get a pointer to Base given the Handle.
//
Base* obj = getObject(h);
// Remove it from the map.
// Delete the object.
if ( obj != nullptr )
{
getObjectMap().erase(h);
delete obj;
}
}
void fun(Handle h, Derived1_tag tag)
{
// Get a pointer to Derived1 given the Handle.
Derived1* obj = getObject<Derived1>(h);
if ( obj == nullptr )
{
// Problem.
// Decide how to deal with it.
return;
}
// Use obj
}
void bar(Handle h, Derived2_tag tag)
{
Derived2* obj = getObject<Derived2>(h);
if ( obj == nullptr )
{
// Problem.
// Decide how to deal with it.
return;
}
// Use obj
}
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 ein Derived
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 nutzen Base
. 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 kann Base
, obwohl sie etwas teurer sind.
Base
, könnten eine normale abstrakte Basisklasse ("Schnittstelle") und konkrete Implementierungen ohne Pimpl ausreichen.