c++ - resueltos - Compruebe si la clase se deriva de una clase específica(compilar, tiempo de ejecución, ambas respuestas disponibles)
programacion en c++ (6)
Es más fácil explicar en un ejemplo,
class base {
//....
}
class derived1 : public base {
//...
}
En mi biblioteca, hay un puntero de clase base. El usuario de la biblioteca debe crear clases derivadas de base o derivadas1 y asignar puntero a esa clase.
¿Cómo puedo verificar de qué clase proviene la clase definida por el usuario?
Compruebe si la clase se deriva de una clase específica (tiempo de compilación)
Puedes usar std::is_base_of
:
#include <type_traits>
....
const bool isBase = std::is_base_of<base, TheOtherClass>::value;
isBase
es verdadero si TheOtherClass
se deriva de la base
.
Podría usar dynamic_cast .
if (dynamic_cast<DerivedClass*>(ptr)) {
std::cout << "Class is derived from DerivedClass.";
}
Puedes hacer esto de varias maneras. Los más comunes como los otros han señalado son dynamic_cast <> y std :: is_base_of . Este último se usa en tiempo de compilación, mientras que dynamic_cast<>
se puede usar en tiempo de ejecución. SIN EMBARGO , dynamic_cast<>
solo funcionará si su clase fuente es polimórfica (es decir, tiene al menos una función virtual, puede ser un método o su destructor). Si no, el compilador generará un error.
El compilador solo aceptará punteros a clases derivadas de su clase base si sus funciones de biblioteca toman punteros a la clase base. Mi respuesta es que con un enfoque clásico, la seguridad lo manejará. Para mi experiencia ese tipo de verificación de tipos es suficiente. Con 25 años de experiencia en la industria, cuestiono la necesidad de hacer este control. Tal vez una pregunta tan fundamental no es bienvenida? Me gustaría ver la justificación para tener la necesidad de hacer este tipo de upcast. Nunca tengo que hacer eso. Lo contrario, es decir, un lanzamiento descendente, lo necesito con bastante frecuencia.
Tengo algunas observaciones sobre las soluciones de tiempo de ejecución en tiempo de ejecución propuestas. Además de cuando se evalúan, is_base_of
y dynamic_cast
tienen requisitos diferentes y sus respuestas pueden ser diferentes.
(1) En primer lugar (como lo señalaron otros) para usar dynamic_cast
, las clases base y derivada deben ser polimórficas (deben tener al menos un método virtual
). is_base_of
no requiere que los tipos sean polimórficos.
(2) Los operandos de is_base_of
son ambos tipos, mientras que dynamic_cast
necesita un tipo (dentro de < >
) y un objeto (dentro ( )
).
(3) dynamic_cast
y is_base_of
pueden dar respuestas diferentes (o uno puede compilar mientras que el otro no) dependiendo del tipo de herencia ( public
vs protected
o private
). Por ejemplo, considere:
struct B { virtual ~B() {} }; // polymorphic, so it can be used in a dynamic_cast
struct D1 : public B {}; // polymorphic by (public) inheritance
struct D2 : private B {}; // polymorphic by (private) inheritance
D1 d1;
D2 d2;
Tenemos
static_assert(std::is_base_of<B, D1>::value, "");
static_assert(std::is_base_of<B, D2>::value, "");
assert(dynamic_cast<B*>(&d1));
assert(!dynamic_cast<B*>(&d2)); // Notice the negation.
En realidad, la última línea produce un error de compilación en GCC ( error: ''B'' is an inaccessible base of ''D2''
). VS2010 lo compila (produciendo solo una advertencia similar al mensaje de error de GCC).
(4) Los requisitos de las clases polimórficas se pueden relajar a través de un truco de manejo de excepciones. Considerar:
struct B { }; // not polymorphic
struct D1 : public B {}; // not polymorphic
struct D2 : private B {}; // not polymorphic
D1 d1;
D2 d2;
template <typename B, typename D>
const B* is_unambiguous_public_base_of(const D* obj) {
try {
throw obj;
}
catch (const B* pb) {
return pb;
}
catch (...) {
}
return nullptr;
}
Entonces nosotros tenemos
static_assert(std::is_base_of<B, D1>::value, "");
static_assert(std::is_base_of<B, D2>::value, "");
assert((is_unambiguous_public_base_of<B>(&d1)));
assert(!(is_unambiguous_public_base_of<B>(&d2))); // Notice the negation.
Vale la pena mencionar que is_unambiguous_public_base_of
es mucho más lenta que dynamic_cast
y (esto se hizo más obvio después del cambio de nombre mencionado en la actualización a continuación) siempre devuelve un nullptr
para los downcasts:
B* b1 = &d1;
assert(dynamic_cast<D1*>(b1)); // Requires D1 and B to be polymorphic.
assert(!(is_unambiguous_public_base_of<D1>(b1))); // Notice the negation.
Una referencia un poco obsoleta en este truco está aquí .
Descargo de responsabilidad: la implementación de is_unambiguous_public_base_of
arriba es solo un borrador para is_unambiguous_public_base_of
el punto y no maneja apropiadamente las calificaciones volatile
.
Actualización : en una versión anterior de esta publicación, is_unambiguous_public_base_of
se llamaba my_dynamic_cast
y esta era una fuente de confusión. Así que lo renombré a un nombre más significativo. (Gracias a Jan Herrmann.)
Creo que la respuesta a esta pregunta es muy difícil. Por supuesto, hay std::is_base_of
y dynamic_cast
. Ambos le brindan cierta información muy limitada. La tercera opción es la sobrecarga de funciones. Con todas estas técnicas, puede elegir una ruta especial en su código que debe ejecutarse.
std::is_base_of
se puede interpretar en un contexto booleano y se deriva de std::true_type
o std::false_type
. Este hecho hace posible usarlo como parámetro para una función y usar el polimorfismo de tiempo de compilación a través de la sobrecarga de función. Este primer ejemplo muestra cómo usarlo en un contexto booleano, pero no tiene más información de tipo específico. Por lo tanto, la compilación fallará en la mayoría de las circunstancias ( ver aquí para una descripción más detallada ):
template<class T>
void do_it1(T const& t) {
if (std::is_base_of<T,derived1>::value) {
// we have a derived1
} else {
// we have a base
}
}
La segunda versión es la sobrecarga de función simple. Aquí se usa el polimorfismo de tiempo de compilación, pero se pierde toda la información del tipo de tiempo de ejecución (excepto que se usan funciones virtuales y dynamic_cast
):
void do_it2(Base const& b) {
// we have a base your algorithm for base here
}
void do_it2(Derived2 const& d) {
// Derived algorithm here
}
Ahora la tercera versión combina ambos:
template<class T>
void do_it3_impl(T const& t, std::true_type) {
// here t will be of a type derived from derived1
}
template<class T>
void do_it3_impl(T const& t,std::false_type) {
// here t will be of type not derived from derived1
}
template<class T>
void do_it_3(T const& t) {
do_it3_impl(t, std::is_base_of<T,derived1>()); // here we forward to our impl
}
La tercera variante se usa normalmente para las bibliotecas de solo encabezado que no usan polimorfismo de tiempo de ejecución (búsqueda de std::advance
para un ejemplo).
Ahora al polimorfismo en tiempo de ejecución. Aquí tienes la variante dynaminc_cast
:
void do_it4(Base const* ptr)
if (derived1 const* obj = dynamic_cast<derived*>(ptr)) {
// here we have obj with type derived1*
} else {
// here we have only base
}
Si esta variante no es lo suficientemente rápida, puede implementar su conversión a onw derivada1:
class derived1;
class base {
// as above
public:
virtual derived1 const* to_derived1() const {
return 0;
}
};
class derived1
: public base
{
// ...
virtual derived1 const* to_derived1() const {
return this;
}
};
void do_it5(Base const* ptr)
if (derived1 const* obj = ptr->to_derived1() {
// here we have obj with type derived1*
} else {
// here we have only base
}
Esto es rápido pero se escala muy bien para muy pocas (aproximadamente 1) clases derivadas.
Por último, pero no menos importante, debe pensar en el diseño de su clase y desear qué métodos hacer virtual e implementar en clases base
, derived1
u otras. Definitivamente debe buscar el patrón de estrategia .