¿Cómo verifico si el tipo de un objeto es una subclase particular en C ++?


82

Estaba pensando en el uso, typeid()pero no sé cómo preguntar si ese tipo es una subclase de otra clase (que, por cierto, es abstracta)


Me pregunto si hay una manera de verificar si el tipo de un objeto es una subclase particular en tiempo de compilación en C ++, porque std::is_base_ofno funcionará como se desea. : 3
KaiserKatze

Respuestas:


38

Realmente no deberías. Si su programa necesita saber qué clase es un objeto, eso generalmente indica un defecto de diseño. Vea si puede obtener el comportamiento que desea usando funciones virtuales. Además, sería útil contar con más información sobre lo que está intentando hacer.

Supongo que tienes una situación como esta:

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

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

Si esto es lo que tiene, intente hacer algo como esto:

class Base
{
  virtual void bar() = 0;
};

class A : public Base
{
  void bar() {/* do X */}
};

class B : public Base
{
  void bar() {/* do Y */}
};

void foo(Base *p)
{
  p->bar();
}

Editar: Dado que el debate sobre esta respuesta aún continúa después de tantos años, pensé que debería incluir algunas referencias. Si tiene un puntero o referencia a una clase base y su código necesita conocer la clase derivada del objeto, entonces viola el principio de sustitución de Liskov . El tío Bob llama a esto un " anatema para el diseño orientado a objetos ".


20
+1. Creo que el nombre correcto para esto es "Diga, no pregunte". Básicamente, siempre favorezca el polimorfismo (DECIRLE a un objeto qué hacer, dejar que la implementación se encargue de ello) sobre un caso / declaración if en la que PREGUNTE para averiguar qué tipo de objeto está tratando.
LeopardSkinPillBoxHat

62
sí, todo esto está bien, pero el chico quería saber cómo resolver el tipo
JohnIdol

7
@Dima, ¿y si alguien quiere conocer la sintaxis solo con fines de aprendizaje (digamos que está leyendo un libro escrito en Java, sobre fallas de diseño y necesita traducirlo a C ++)?
patchwork

8
@Dima ¿Alguna vez ha trabajado con una biblioteca externa que define superclases? Intente aplicar su respuesta allí, por favor.
Tomáš Zato - Reincorporación a Monica

13
Esta respuesta hace la suposición bastante grande de que tiene control sobre los tipos a los que necesita transmitir y puede reescribirlos ... Por ejemplo, estoy agregando una función a una biblioteca GUI basada en otra biblioteca GUI, y necesito saber si el padre de un widget es desplazable. La biblioteca original no ofrece forma de probar esto, así que tengo que intentar convertir mis widgets principales en la clase base de widgets desplazables, lo que realmente apesta. De todos modos, el punto es que omitió la respuesta real a la pregunta en cuestión.
AnorZaken

125

 

class Base
{
  public: virtual ~Base() {}
};

class D1: public Base {};

class D2: public Base {};

int main(int argc,char* argv[]);
{
  D1   d1;
  D2   d2;

  Base*  x = (argc > 2)?&d1:&d2;

  if (dynamic_cast<D2*>(x) == nullptr)
  {
    std::cout << "NOT A D2" << std::endl;
  }
  if (dynamic_cast<D1*>(x) == nullptr)
  {
    std::cout << "NOT A D1" << std::endl;
  }
}

1
¿Realmente necesitas un dynamic_cast<>aquí? ¿No static_cast<>sería suficiente?
krlmlr

15
@krlmlr. ¿Puedes decir el tipo de xen tiempo de compilación? Si es así static_cast<>(), funcionaría. Si no se puede saber el tipo de xhasta el tiempo de ejecución, entonces usted necesitadynamic_cast<>()
Martin York

Gracias. Estoy usando downcasts principalmente en el CRTP, sigo olvidándome de otros casos de uso ;-)
krlmlr

Buena respuesta, pero algo a tener en cuenta aquí. El operador condicional ternario requiere que su segundo y tercer operandos tengan el mismo tipo. Por lo tanto, no sé cómo puede funcionar esto para alguien de esta manera, use un if / else en su lugar. ¿Quizás esto funcionó en el pasado? De todos modos.
Nikos

@Nikos, funciona porque: 1. C ++ no requiere que los casos ternarios sean del mismo tipo, 2. Son un tipo de puntero de clase derivada, y el puntero de clase derivado se convierte implícitamente en la base.
hazer_hazer

30

Puedes hacerlo con dynamic_cast(al menos para tipos polimórficos).

En realidad, pensándolo bien, no puede decir si es ESPECÍFICAMENTE un tipo en particular con, dynamic_castpero puede decir si es ese tipo o alguna subclase del mismo.

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}

¿Cuándo una subclase no es un tipo polimórfico?
OJFord

6
@OllieFord: cuando no hay funciones virtuales.
Drew Hall

Dicho de otra manera, cuando std::is_polymorphic_v<T>sea false.
Xeverous

7

El siguiente código muestra 3 formas diferentes de hacerlo:

  • función virtual
  • typeid
  • Dynamic_cast
#include <iostream>
#include <typeinfo>
#include <typeindex>

enum class Type {Base, A, B};

class Base {
public:
    virtual ~Base() = default;
    virtual Type type() const {
        return Type::Base;
    }
};

class A : public Base {
    Type type() const override {
        return Type::A;
    }
};

class B : public Base {
    Type type() const override {
        return Type::B;
    }
};

int main()
{
    const char *typemsg;
    A a;
    B b;
    Base *base = &a;             // = &b;    !!!!!!!!!!!!!!!!!
    Base &bbb = *base;

    // below you can replace    base    with  &bbb    and get the same results

    // USING virtual function
    // ======================
    // classes need to be in your control
    switch(base->type()) {
    case Type::A:
        typemsg = "type A";
        break;
    case Type::B:
        typemsg = "type B";
        break;
    default:
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING typeid
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    std::type_index ti(typeid(*base));
    if (ti == std::type_index(typeid(A))) {
        typemsg = "type A";
    } else if (ti == std::type_index(typeid(B))) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING dynamic_cast
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    if (dynamic_cast</*const*/ A*>(base)) {
        typemsg = "type A";
    } else if (dynamic_cast</*const*/ B*>(base)) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;
}

El programa de arriba imprime esto:

type A
type A
type A

6

dynamic_castpuede determinar si el tipo contiene el tipo de destino en algún lugar de la jerarquía de herencia (sí, es una característica poco conocida que si Bhereda de Ay C, puede convertir una A*directamente en a C*). typeid()puede determinar el tipo exacto del objeto. Sin embargo, ambos deben usarse con extrema moderación. Como ya se mencionó, siempre debe evitar la identificación dinámica de tipos, ya que indica un defecto de diseño. (Además, si sabe que el objeto es seguro del tipo de objetivo, puede hacer un abatimiento con un static_cast. Boost ofrece un polymorphic_downcastque hará un abatimiento con dynamic_casty asserten modo de depuración, y en el modo de lanzamiento solo usará a static_cast).


4

No estoy de acuerdo en que nunca debería querer verificar el tipo de un objeto en C ++. Si puede evitarlo, estoy de acuerdo en que debería hacerlo. Sin embargo, decir que NUNCA debes hacer esto bajo ninguna circunstancia es ir demasiado lejos. Puede hacer esto en muchos idiomas y puede hacer su vida mucho más fácil. Howard Pinsley, por ejemplo, nos mostró cómo en su publicación sobre C #.

Trabajo mucho con Qt Framework. En general, modelo lo que hago según la forma en que hacen las cosas (al menos cuando trabajan en su marco). La clase QObject es la clase base de todos los objetos Qt. Esa clase tiene las funciones isWidgetType () e isWindowType () como una verificación rápida de subclase. Entonces, ¿por qué no poder verificar sus propias clases derivadas, que son comparables en su naturaleza? Aquí hay un derivado de QObject de algunas de estas otras publicaciones:

class MyQObject : public QObject
{
public:
    MyQObject( QObject *parent = 0 ) : QObject( parent ){}
    ~MyQObject(){}

    static bool isThisType( const QObject *qObj )
    { return ( dynamic_cast<const MyQObject*>(qObj) != NULL ); }
};

Y luego, cuando pasa un puntero a un QObject, puede verificar si apunta a su clase derivada llamando a la función miembro estática:

if( MyQObject::isThisType( qObjPtr ) ) qDebug() << "This is a MyQObject!";

4

No sé si entiendo correctamente su problema, así que permítame repetirlo con mis propias palabras ...

Problema: Dadas las clases By D, determine si Des una subclase de B(¿o viceversa?)

Solución: ¡Usa un poco de magia de plantilla! Bien, en serio, debes echar un vistazo a LOKI, una excelente biblioteca de metaprogramación de plantillas producida por el legendario autor de C ++ Andrei Alexandrescu.

Más específicamente, descargue LOKI e incluya el encabezado TypeManip.hen su código fuente y luego use la SuperSubclassplantilla de clase de la siguiente manera:

if(SuperSubClass<B,D>::value)
{
...
}

De acuerdo con la documentación, SuperSubClass<B,D>::valueserá verdadero si Bes una base pública de D, o si By Dson alias del mismo tipo.

es decir, Des una subclase de Bo Des lo mismo que B.

Espero que esto ayude.

editar:

Tenga en cuenta que la evaluación de SuperSubClass<B,D>::valueocurre en tiempo de compilación a diferencia de algunos métodos que lo usan dynamic_cast, por lo tanto, no hay penalización por usar este sistema en tiempo de ejecución.


3
#include <stdio.h>
#include <iostream.h>

class Base
{
  public: virtual ~Base() {}

  template<typename T>
  bool isA() {
    return (dynamic_cast<T*>(this) != NULL);
  }
};

class D1: public Base {};
class D2: public Base {};
class D22: public D2 {};

int main(int argc,char* argv[]);
{
  D1*   d1  = new D1();
  D2*   d2  = new D2();
  D22*  d22 = new D22();

  Base*  x = d22;

  if( x->isA<D22>() )
  {
    std::cout << "IS A D22" << std::endl;
  }
  if( x->isA<D2>() )
  {
    std::cout << "IS A D2" << std::endl;
  }
  if( x->isA<D1>() )
  {
    std::cout << "IS A D1" << std::endl;
  }
  if(x->isA<Base>() )
  {
    std::cout << "IS A Base" << std::endl;
  }
}

Resultado:

IS A D22
IS A D2
IS A Base

2

Estaba pensando en el uso de typeid()...

Bueno, sí, se podría hacer mediante la comparación: typeid().name(). Si tomamos la situación ya descrita, donde:

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

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

Una posible implementación de foo(Base *p)sería:

#include <typeinfo>

void foo(Base *p)
{
    if(typeid(*p) == typeid(A))
    {
        // the pointer is pointing to the derived class A
    }  
    else if (typeid(*p).name() == typeid(B).name()) 
    {
        // the pointer is pointing to the derived class B
    }
}

1
¿Por qué combinas la combinación de typeid (). Name () y typeid ()? ¿Por qué no comparar siempre typeid ()?
Silicomancer

1

Solo puede hacerlo en tiempo de compilación usando plantillas, a menos que use RTTI.

Te permite usar la función typeid que producirá un puntero a una estructura type_info que contiene información sobre el tipo.

Leer sobre ello en Wikipedia


Votó a favor de mencionar RTTI en este contexto, que todos los demás simplemente ignoraron.
ManuelSchneid3r


0

Puede hacerlo con plantillas (o SFINAE (La falla de sustitución no es un error)). Ejemplo:

#include <iostream>

class base
{
public:
    virtual ~base() = default;
};

template <
    class type,
    class = decltype(
        static_cast<base*>(static_cast<type*>(0))
    )
>
bool check(type)
{
    return true;
}

bool check(...)
{
    return false;
}

class child : public base
{
public:
    virtual ~child() = default;
};

class grandchild : public child {};

int main()
{
    std::cout << std::boolalpha;

    std::cout << "base:       " << check(base())       << '\n';
    std::cout << "child:      " << check(child())      << '\n';
    std::cout << "grandchild: " << check(grandchild()) << '\n';
    std::cout << "int:        " << check(int())        << '\n';

    std::cout << std::flush;
}

Salida:

base:       true
child:      true
grandchild: true
int:        false
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.