Tengo una jerarquía de clases para la que me gustaría separar la interfaz de la implementación. Mi solución es tener dos jerarquías: una jerarquía de clase de identificador para la interfaz y una jerarquía de clase no pública para la implementación. La clase de identificador base tiene un puntero a implementación que las clases de identificador derivadas convierten a un puntero del tipo derivado (ver función getPimpl()
).
Aquí hay un boceto de mi solución para una clase base con dos clases derivadas. ¿Hay una mejor solución?
Archivo "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;
...
};
Archivo "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
, una clase base abstracta normal ("interfaz") e implementaciones concretas sin pimpl podrían ser suficientes.Respuestas:
Creo que es una estrategia pobre de la que
Derived_1::Impl
derivarBase::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 deBase::Impl
, has derrotado ese propósito. Ahora, no soloBase
dependeBase::Impl
la implementación de , sino queDerived_1
también depende de la implementación deBase::Impl
.Eso depende de qué compensaciones sean aceptables para usted.
Solución 1
Haz
Impl
clases totalmente independientes. Esto implicará que habrá dos punteros a lasImpl
clases: uno enBase
y otro enDerived_N
.Solución 2
Exponga las clases solo como identificadores. No exponga las definiciones de clase e implementaciones en absoluto.
Archivo de encabezado público:
Aquí está la implementación rápida
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 unDerived
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 esBase
. 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 miembroBase
a pesar de que es un poco más caro.fuente
La única mejora que puedo ver aquí es dejar que las clases concretas definan el campo de implementación. Si las clases base abstractas lo necesitan, pueden definir una propiedad abstracta que sea fácil de implementar en las clases concretas:
Base.h
Base.cpp
Esto parece ser más seguro para mí. Si tiene un árbol grande, también puede introducirlo
virtual std::shared_ptr<Impl1> getImpl1() =0
en el medio del árbol.fuente