Encontrar el tipo de un objeto en C ++

147

Tengo una clase A y otra clase que hereda de ella, B. Estoy anulando una función que acepta un objeto de tipo A como parámetro, por lo que tengo que aceptar una A. Sin embargo, más tarde llamo funciones que solo B tiene, así que quiero devolver falso y no continuar si el objeto pasado no es de tipo B.

¿Cuál es la mejor manera de averiguar qué tipo es el objeto pasado a mi función?

lemnisca
fuente

Respuestas:

162

Dynamic_cast debería hacer el truco

TYPE& dynamic_cast<TYPE&> (object);
TYPE* dynamic_cast<TYPE*> (object);

La dynamic_castpalabra clave arroja un dato desde un puntero o tipo de referencia a otro, realizando una verificación de tiempo de ejecución para garantizar la validez del reparto.

Si intenta lanzar un puntero a un tipo que no es un tipo de objeto real, el resultado del lanzamiento será NULL. Si intentas lanzar para hacer referencia a un tipo que no es un tipo de objeto real, el lanzamiento arrojará una bad_castexcepción.

Asegúrese de que haya al menos una función virtual en la clase Base para hacer que dynamic_cast funcione.

Tema de Wikipedia Información de tipo de tiempo de ejecución

RTTI está disponible solo para clases que son polimórficas, lo que significa que tienen al menos un método virtual. En la práctica, esto no es una limitación porque las clases base deben tener un destructor virtual para permitir que los objetos de las clases derivadas realicen una limpieza adecuada si se eliminan de un puntero base.

yesraaj
fuente
1
¿Qué quiere decir con que debe haber una función virtual en la clase Base para hacer que dynamic_cast funcione? Eso me parece importante, que solo adivinaré.
GiCo
3
OK lo encontró: la información de tipo de tiempo de ejecución (RTTI) está disponible solo para clases que son polimórficas, lo que significa que tienen al menos un método virtual. dynamic_cast y typeid necesitan RTTI.
GiCo
¿No dynamic_casttira si no es convertible? ¿Hay alguna manera de hacerlo sin generar un lanzamiento?
jww
A* aptr = dynamic_cast<A*>(ptr);// ¿no se supone que es así
Mehdi Karamosly
¿Funciona esto para los POD que se han lanzado a un uint8_t*? Es decir, ¿puedo comprobar eso uint32_t* x = dynamic_cast<uint32_t*>(p), dónde pestá uint8_t*? (Estoy tratando de encontrar una prueba para infringir las infracciones).
jww
157

El reparto dinámico es el mejor para su descripción del problema, pero solo quiero agregar que puede encontrar el tipo de clase con:

#include <typeinfo>

...
string s = typeid(YourClass).name()
Robocida
fuente
44
Bien si realmente no sabes cuál es tu objeto. La respuesta aceptada supone que sí.
deshacer
44
@xus Sí. es parte de los encabezados
estándar
8
No veo como No se requiere que los nombres de identificación de tipo sean útiles y su implementación está definida.
Zapato
3
Lo más interesante aquí: los nombres de instancias de la misma clase no tienen que ser iguales. Sin embargo, el tipo de letra tiene que comparar igual para instancias de la misma clase, consulte stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
Cuarenta
1
Nota: gcc devuelve el nombre magled, por ejemplo 11MyClass. Para deshacer, puede usar la biblioteca de extensiones ABI en cxxabi.h. Esto te abi::__cxa_demangleda el nombre real
David G
27

Esto se llama RTTI , pero seguramente querrá reconsiderar su diseño aquí, porque encontrar el tipo y hacer cosas especiales basadas en él hace que su código sea más frágil.

Ana Betts
fuente
44
Cierto. Por desgracia estoy trabajando en un proyecto existente, de modo que no puedo ir cambiando el diseño, ni nada en la clase A.
lemnisca
11

Solo para completar, construiré una compilación de Robocide y señalaré que typeidse puede usar solo sin usar name ():

#include <typeinfo>
#include <iostream>

using namespace std;

class A {
public:
    virtual ~A() = default; // We're not polymorphic unless we
                            // have a virtual function.
};
class B : public A { } ;
class C : public A { } ;

int
main(int argc, char* argv[])
{
    B b;
    A& a = b;

    cout << "a is B: " << boolalpha << (typeid(a) == typeid(B)) << endl;
    cout << "a is C: " << boolalpha << (typeid(a) == typeid(C)) << endl;
    cout << "b is B: " << boolalpha << (typeid(b) == typeid(B)) << endl;
    cout << "b is A: " << boolalpha << (typeid(b) == typeid(A)) << endl;
    cout << "b is C: " << boolalpha << (typeid(b) == typeid(C)) << endl;
}

Salida:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false
firebush
fuente
9

Probablemente incruste en sus objetos una "etiqueta" ID y úsela para distinguir entre objetos de clase A y objetos de clase B.

Sin embargo, esto muestra una falla en el diseño. Idealmente, aquellos métodos en B que A no tiene, deberían ser parte de A pero quedar vacíos, y B los sobrescribe. Esto elimina el código específico de la clase y está más en el espíritu de OOP.

espacio libre
fuente
8

Estás buscando dynamic_cast<B*>(pointer)

Joshua
fuente
4

Porque tu clase no es polimórfica. Tratar:

struct BaseClas { int base; virtual ~BaseClas(){} };
class Derived1 : public BaseClas { int derived1; };

Ahora BaseClases polimórfico. Cambié la clase a struct porque los miembros de una estructura son públicos por defecto.

c64zottel
fuente
3

Tu descripción es un poco confusa.

En términos generales, aunque algunas implementaciones de C ++ tienen mecanismos para ello, no se debe preguntar sobre el tipo. En su lugar, se supone que debe hacer un dynamic_cast en el puntero a A. Lo que esto hará es que, en tiempo de ejecución, se verificará el contenido real del puntero a A. Si tiene una B, obtendrá su puntero a B. De lo contrario, obtendrá una excepción o nulo.

Uri
fuente
1
Cabe señalar que obtendrá una excepción solo si realiza una conversión de referencia que falla. es decir, Dynamic_cast <T &> (t). Los lanzamientos de puntero fallidos devuelven NULL. es decir, dynamic_cast <T *> (t)
AlfaZulu
Sí, debería haberlo aclarado mejor. Gracias. Desearía que hubiera una palabra que se describe en los tipos C que son por referencia en lugar de por valor.
Uri
3

Como otros indicaron, puedes usar dynamic_cast. Pero, en general, usar dynamic_cast para descubrir el tipo de la clase derivada en la que está trabajando indica un mal diseño. Si está anulando una función que toma el puntero de A como parámetro, entonces debería poder trabajar con los métodos / datos de la clase A y no debería depender de los datos de la clase B. En su caso, en lugar de anular si está seguro de que el método que está escribiendo funcionará solo con la clase B, entonces debe escribir un nuevo método en la clase B.

Naveen
fuente
1

Usar funciones sobrecargadas. No requiere Dynamic_cast o incluso soporte RTTI:

class A {};
class B : public A {};

class Foo {
public:
    void Bar(A& a) {
        // do something
    }
    void Bar(B& b) {
        Bar(static_cast<A&>(b));
        // do B specific stuff
    }
};
jmucchiello
fuente
Directamente de la pregunta original: "Luego llamo funciones que solo B tiene": ¿cómo funcionaría la sobrecarga en tal caso?
Marcin Gil
Cuando llamas a Bar con una A, no sucede nada de B. Cuando llama a Bar con una B, se pueden invocar métodos que solo existen en B. ¿Lees la pregunta original? Bar es su "Estoy anulando una función que acepta un objeto de tipo A como parámetro"
jmucchiello
77
Esto no funciona con el polimorfismo dinámico, que sospecho que está utilizando el interrogador. C ++ no puede seleccionar una sobrecarga basada en la clase de tiempo de ejecución del parámetro, solo en función del tipo de tiempo de compilación.
Steve Jessop
1

Si puede acceder a la biblioteca de impulso, tal vez lo que necesita sea la función type_id_with_cvr () , que puede proporcionar el tipo de datos sin eliminar los modificadores constantes, volátiles, y && . Aquí hay un ejemplo simple en C ++ 11:

#include <iostream>
#include <boost/type_index.hpp>

int a;
int& ff() 
{
    return a;
}

int main() {
    ff() = 10;
    using boost::typeindex::type_id_with_cvr;
    std::cout << type_id_with_cvr<int&>().pretty_name() << std::endl;
    std::cout << type_id_with_cvr<decltype(ff())>().pretty_name() << std::endl;
    std::cout << typeid(ff()).name() << std::endl;
}

Espero que esto sea útil.

Kehe CAI
fuente