Creo que es una estrategia pobre de la que Derived_1::Impl
derivar Base::Impl
.
El propósito principal de usar el idioma Pimpl es ocultar los detalles de implementación de una clase. Al dejar Derived_1::Impl
derivar de Base::Impl
, has derrotado ese propósito. Ahora, no solo Base
depende Base::Impl
la implementación de , sino que Derived_1
también depende de la implementación de Base::Impl
.
¿Hay una mejor solución?
Eso depende de qué compensaciones sean aceptables para usted.
Solución 1
Haz Impl
clases totalmente independientes. Esto implicará que habrá dos punteros a las Impl
clases: uno en Base
y otro en 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;
};
Solución 2
Exponga las clases solo como identificadores. No exponga las definiciones de clase e implementaciones en absoluto.
Archivo de encabezado público:
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);
Aquí está la implementación rápida
#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
}
Pros y contras
Con el primer enfoque, puede construir Derived
clases en la pila. Con el segundo enfoque, esa no es una opción.
Con el primer enfoque, incurrirá en el costo de dos asignaciones dinámicas y desasignaciones para construir y destruir un Derived
en la pila. Si construye y destruye un Derived
objeto del montón, incurra en el costo de una asignación y desasignación más. Con el segundo enfoque, solo incurrirá en el costo de una asignación dinámica y una desasignación por cada objeto.
Con el primer enfoque, puede usar la virtual
función miembro es Base
. Con el segundo enfoque, esa no es una opción.
Mi sugerencia
Iría con la primera solución para poder usar la jerarquía de clases y las virtual
funciones de miembro Base
a pesar de que es un poco más caro.
Base
, una clase base abstracta normal ("interfaz") e implementaciones concretas sin pimpl podrían ser suficientes.