C ++ equivalente de la instancia de Java de

202

¿Cuál es el método preferido para lograr el equivalente en C ++ de Java instanceof?

Yuval Adam
fuente
57
Preferido por rendimiento y compatibilidad ...
Yuval Adam
77
¿No es justo preguntar "instanceof - en qué idioma?"
mysticcoder
3
@mysticcoder: Obtengo "например на" para búlgaro, aunque GT no es compatible con C ++
Mark K Cowan

Respuestas:

200

Intenta usar:

if(NewType* v = dynamic_cast<NewType*>(old)) {
   // old was safely casted to NewType
   v->doSomething();
}

Esto requiere que su compilador tenga habilitado el soporte rtti.

EDITAR: ¡He tenido algunos buenos comentarios sobre esta respuesta!

Cada vez que necesite usar un dynamic_cast (o una instancia de), será mejor que se pregunte si es algo necesario. Generalmente es una señal de mal diseño.

Las soluciones típicas son poner el comportamiento especial para la clase que está comprobando en una función virtual en la clase base o tal vez introducir algo como un visitante donde puede introducir un comportamiento específico para las subclases sin cambiar la interfaz (excepto para agregar la interfaz de aceptación del visitante de curso).

Como se señaló, dynamic_cast no viene gratis. Un truco simple y de rendimiento constante que maneja la mayoría (pero no todos los casos) es básicamente agregar una enumeración que represente todos los tipos posibles que su clase puede tener y verificar si obtuvo el correcto.

if(old->getType() == BOX) {
   Box* box = static_cast<Box*>(old);
   // Do something box specific
}

Este no es un buen diseño, pero puede ser una solución alternativa y su costo es más o menos solo una llamada de función virtual. También funciona independientemente de que RTTI esté habilitado o no.

Tenga en cuenta que este enfoque no admite múltiples niveles de herencia, por lo que si no tiene cuidado, puede terminar con un código como este:

// Here we have a SpecialBox class that inherits Box, since it has its own type
// we must check for both BOX or SPECIAL_BOX
if(old->getType() == BOX || old->getType() == SPECIAL_BOX) {
   Box* box = static_cast<Box*>(old);
   // Do something box specific
}
Laserallan
fuente
44
Ese es generalmente el caso cuando haces una verificación de "instancia de"
Laserallan
77
Si tiene que usar instanceof, hay, en la mayoría de los casos, algo mal con su diseño.
mslot 01 de
24
No olvide que dynamic_cast es una operación con un gran costo.
Klaim 01 de
13
Hay muchos ejemplos de usos razonables de las pruebas de tipo dinámico. Por lo general, no se prefiere, pero tiene un lugar. (De lo contrario, ¿por qué aparecería o su equivalente en todos los principales lenguajes OO: C ++, Java, Python, etc.?)
Paul Draper
2
Capturaría ambos en el nivel IOException si no necesitan ser manejados de manera diferente. Si necesitan ser manejados de manera diferente, entonces agregaría un bloque catch para cada excepción.
mslot
37

Dependiendo de lo que quieras hacer, podrías hacer esto:

template<typename Base, typename T>
inline bool instanceof(const T*) {
    return std::is_base_of<Base, T>::value;
}

Utilizar:

if (instanceof<BaseClass>(ptr)) { ... }

Sin embargo, esto opera únicamente en los tipos conocidos por el compilador.

Editar:

Este código debería funcionar para punteros polimórficos:

template<typename Base, typename T>
inline bool instanceof(const T *ptr) {
    return dynamic_cast<const Base*>(ptr) != nullptr;
}

Ejemplo: http://cpp.sh/6qir

panzi
fuente
Solución elegante y bien hecha. +1 Pero tenga cuidado de obtener el puntero correcto. ¿No es válido para el puntero polimórfico?
Adrian Maire
¿Qué pasa si deferenciamos el puntero cuando usamos esta función? ¿Funcionaría entonces para punteros polimórficos?
mark.kedzierski
No, esto solo funciona en los tipos conocidos por el compilador. No funcionará con punteros polimórficos, sin importar si desreferencia o no. Sin embargo, agregaré algo que podría funcionar en ese caso.
panzi
2
He modificado su ejemplo para escribir una versión de este método que utiliza referencias en lugar de punteros: cpp.sh/8owv
Sri Harsha Chilakapati
¿Por qué el tipo de destino del elenco dinámico "const"?
user1056903
7

Instancia de implementación sin Dynamic_cast

Creo que esta pregunta sigue siendo relevante hoy. Usando el estándar C ++ 11 ahora puede implementar una instanceoffunción sin usar dynamic_castasí:

if (dynamic_cast<B*>(aPtr) != nullptr) {
  // aPtr is instance of B
} else {
  // aPtr is NOT instance of B
}

Pero aún depende del RTTIsoporte. Así que aquí está mi solución para este problema dependiendo de algunas Macros y Metaprogramming Magic. El único inconveniente es que este enfoque no funciona para la herencia múltiple .

InstanceOfMacros.h

#include <set>
#include <tuple>
#include <typeindex>

#define _EMPTY_BASE_TYPE_DECL() using BaseTypes = std::tuple<>;
#define _BASE_TYPE_DECL(Class, BaseClass) \
  using BaseTypes = decltype(std::tuple_cat(std::tuple<BaseClass>(), Class::BaseTypes()));
#define _INSTANCE_OF_DECL_BODY(Class)                                 \
  static const std::set<std::type_index> baseTypeContainer;           \
  virtual bool instanceOfHelper(const std::type_index &_tidx) {       \
    if (std::type_index(typeid(ThisType)) == _tidx) return true;      \
    if (std::tuple_size<BaseTypes>::value == 0) return false;         \
    return baseTypeContainer.find(_tidx) != baseTypeContainer.end();  \
  }                                                                   \
  template <typename... T>                                            \
  static std::set<std::type_index> getTypeIndexes(std::tuple<T...>) { \
    return std::set<std::type_index>{std::type_index(typeid(T))...};  \
  }

#define INSTANCE_OF_SUB_DECL(Class, BaseClass) \
 protected:                                    \
  using ThisType = Class;                      \
  _BASE_TYPE_DECL(Class, BaseClass)            \
  _INSTANCE_OF_DECL_BODY(Class)

#define INSTANCE_OF_BASE_DECL(Class)                                                    \
 protected:                                                                             \
  using ThisType = Class;                                                               \
  _EMPTY_BASE_TYPE_DECL()                                                               \
  _INSTANCE_OF_DECL_BODY(Class)                                                         \
 public:                                                                                \
  template <typename Of>                                                                \
  typename std::enable_if<std::is_base_of<Class, Of>::value, bool>::type instanceOf() { \
    return instanceOfHelper(std::type_index(typeid(Of)));                               \
  }

#define INSTANCE_OF_IMPL(Class) \
  const std::set<std::type_index> Class::baseTypeContainer = Class::getTypeIndexes(Class::BaseTypes());

Manifestación

Luego puede usar estas cosas ( con precaución ) de la siguiente manera:

DemoClassHierarchy.hpp *

#include "InstanceOfMacros.h"

struct A {
  virtual ~A() {}
  INSTANCE_OF_BASE_DECL(A)
};
INSTANCE_OF_IMPL(A)

struct B : public A {
  virtual ~B() {}
  INSTANCE_OF_SUB_DECL(B, A)
};
INSTANCE_OF_IMPL(B)

struct C : public A {
  virtual ~C() {}
  INSTANCE_OF_SUB_DECL(C, A)
};
INSTANCE_OF_IMPL(C)

struct D : public C {
  virtual ~D() {}
  INSTANCE_OF_SUB_DECL(D, C)
};
INSTANCE_OF_IMPL(D)

El siguiente código presenta una pequeña demostración para verificar el comportamiento correcto rudimentario.

InstanceOfDemo.cpp

#include <iostream>
#include <memory>
#include "DemoClassHierarchy.hpp"

int main() {
  A *a2aPtr = new A;
  A *a2bPtr = new B;
  std::shared_ptr<A> a2cPtr(new C);
  C *c2dPtr = new D;
  std::unique_ptr<A> a2dPtr(new D);

  std::cout << "a2aPtr->instanceOf<A>(): expected=1, value=" << a2aPtr->instanceOf<A>() << std::endl;
  std::cout << "a2aPtr->instanceOf<B>(): expected=0, value=" << a2aPtr->instanceOf<B>() << std::endl;
  std::cout << "a2aPtr->instanceOf<C>(): expected=0, value=" << a2aPtr->instanceOf<C>() << std::endl;
  std::cout << "a2aPtr->instanceOf<D>(): expected=0, value=" << a2aPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "a2bPtr->instanceOf<A>(): expected=1, value=" << a2bPtr->instanceOf<A>() << std::endl;
  std::cout << "a2bPtr->instanceOf<B>(): expected=1, value=" << a2bPtr->instanceOf<B>() << std::endl;
  std::cout << "a2bPtr->instanceOf<C>(): expected=0, value=" << a2bPtr->instanceOf<C>() << std::endl;
  std::cout << "a2bPtr->instanceOf<D>(): expected=0, value=" << a2bPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "a2cPtr->instanceOf<A>(): expected=1, value=" << a2cPtr->instanceOf<A>() << std::endl;
  std::cout << "a2cPtr->instanceOf<B>(): expected=0, value=" << a2cPtr->instanceOf<B>() << std::endl;
  std::cout << "a2cPtr->instanceOf<C>(): expected=1, value=" << a2cPtr->instanceOf<C>() << std::endl;
  std::cout << "a2cPtr->instanceOf<D>(): expected=0, value=" << a2cPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "c2dPtr->instanceOf<A>(): expected=1, value=" << c2dPtr->instanceOf<A>() << std::endl;
  std::cout << "c2dPtr->instanceOf<B>(): expected=0, value=" << c2dPtr->instanceOf<B>() << std::endl;
  std::cout << "c2dPtr->instanceOf<C>(): expected=1, value=" << c2dPtr->instanceOf<C>() << std::endl;
  std::cout << "c2dPtr->instanceOf<D>(): expected=1, value=" << c2dPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "a2dPtr->instanceOf<A>(): expected=1, value=" << a2dPtr->instanceOf<A>() << std::endl;
  std::cout << "a2dPtr->instanceOf<B>(): expected=0, value=" << a2dPtr->instanceOf<B>() << std::endl;
  std::cout << "a2dPtr->instanceOf<C>(): expected=1, value=" << a2dPtr->instanceOf<C>() << std::endl;
  std::cout << "a2dPtr->instanceOf<D>(): expected=1, value=" << a2dPtr->instanceOf<D>() << std::endl;

  delete a2aPtr;
  delete a2bPtr;
  delete c2dPtr;

  return 0;
}

Salida:

a2aPtr->instanceOf<A>(): expected=1, value=1
a2aPtr->instanceOf<B>(): expected=0, value=0
a2aPtr->instanceOf<C>(): expected=0, value=0
a2aPtr->instanceOf<D>(): expected=0, value=0

a2bPtr->instanceOf<A>(): expected=1, value=1
a2bPtr->instanceOf<B>(): expected=1, value=1
a2bPtr->instanceOf<C>(): expected=0, value=0
a2bPtr->instanceOf<D>(): expected=0, value=0

a2cPtr->instanceOf<A>(): expected=1, value=1
a2cPtr->instanceOf<B>(): expected=0, value=0
a2cPtr->instanceOf<C>(): expected=1, value=1
a2cPtr->instanceOf<D>(): expected=0, value=0

c2dPtr->instanceOf<A>(): expected=1, value=1
c2dPtr->instanceOf<B>(): expected=0, value=0
c2dPtr->instanceOf<C>(): expected=1, value=1
c2dPtr->instanceOf<D>(): expected=1, value=1

a2dPtr->instanceOf<A>(): expected=1, value=1
a2dPtr->instanceOf<B>(): expected=0, value=0
a2dPtr->instanceOf<C>(): expected=1, value=1
a2dPtr->instanceOf<D>(): expected=1, value=1

Actuación

La pregunta más interesante que surge ahora es si estas cosas malvadas son más eficientes que el uso de dynamic_cast. Por lo tanto, he escrito una aplicación de medición de rendimiento muy básica.

InstanceOfPerformance.cpp

#include <chrono>
#include <iostream>
#include <string>
#include "DemoClassHierarchy.hpp"

template <typename Base, typename Derived, typename Duration>
Duration instanceOfMeasurement(unsigned _loopCycles) {
  auto start = std::chrono::high_resolution_clock::now();
  volatile bool isInstanceOf = false;
  for (unsigned i = 0; i < _loopCycles; ++i) {
    Base *ptr = new Derived;
    isInstanceOf = ptr->template instanceOf<Derived>();
    delete ptr;
  }
  auto end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<Duration>(end - start);
}

template <typename Base, typename Derived, typename Duration>
Duration dynamicCastMeasurement(unsigned _loopCycles) {
  auto start = std::chrono::high_resolution_clock::now();
  volatile bool isInstanceOf = false;
  for (unsigned i = 0; i < _loopCycles; ++i) {
    Base *ptr = new Derived;
    isInstanceOf = dynamic_cast<Derived *>(ptr) != nullptr;
    delete ptr;
  }
  auto end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<Duration>(end - start);
}

int main() {
  unsigned testCycles = 10000000;
  std::string unit = " us";
  using DType = std::chrono::microseconds;

  std::cout << "InstanceOf performance(A->D)  : " << instanceOfMeasurement<A, D, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "InstanceOf performance(A->C)  : " << instanceOfMeasurement<A, C, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "InstanceOf performance(A->B)  : " << instanceOfMeasurement<A, B, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "InstanceOf performance(A->A)  : " << instanceOfMeasurement<A, A, DType>(testCycles).count() << unit
            << "\n"
            << std::endl;
  std::cout << "DynamicCast performance(A->D) : " << dynamicCastMeasurement<A, D, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "DynamicCast performance(A->C) : " << dynamicCastMeasurement<A, C, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "DynamicCast performance(A->B) : " << dynamicCastMeasurement<A, B, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "DynamicCast performance(A->A) : " << dynamicCastMeasurement<A, A, DType>(testCycles).count() << unit
            << "\n"
            << std::endl;
  return 0;
}

Los resultados varían y se basan esencialmente en el grado de optimización del compilador. Compilar el programa de medición de rendimiento utilizando g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppla salida en mi máquina local fue:

InstanceOf performance(A->D)  : 699638 us
InstanceOf performance(A->C)  : 642157 us
InstanceOf performance(A->B)  : 671399 us
InstanceOf performance(A->A)  : 626193 us

DynamicCast performance(A->D) : 754937 us
DynamicCast performance(A->C) : 706766 us
DynamicCast performance(A->B) : 751353 us
DynamicCast performance(A->A) : 676853 us

Mhm, este resultado fue muy aleccionador, porque los tiempos demuestran que el nuevo enfoque no es mucho más rápido en comparación con el dynamic_castenfoque. Es incluso menos eficiente para el caso de prueba especial que prueba si un puntero de Aes una instancia de A. PERO la marea cambia ajustando nuestro binario usando la compilación otpimization. El comando del compilador respectivo es g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. El resultado en mi máquina local fue sorprendente:

InstanceOf performance(A->D)  : 3035 us
InstanceOf performance(A->C)  : 5030 us
InstanceOf performance(A->B)  : 5250 us
InstanceOf performance(A->A)  : 3021 us

DynamicCast performance(A->D) : 666903 us
DynamicCast performance(A->C) : 698567 us
DynamicCast performance(A->B) : 727368 us
DynamicCast performance(A->A) : 3098 us

Si no depende de la herencia múltiple, no se opone a las viejas macros C, RTTI y metaprogramación de plantillas y no es demasiado vago para agregar algunas pequeñas instrucciones a las clases de su jerarquía de clases, entonces este enfoque puede impulsar un poco su aplicación con respecto a su rendimiento, si a menudo termina comprobando la instancia de un puntero. Pero úsalo con precaución . No hay garantía para la corrección de este enfoque.

Nota: Todas las demostraciones se compilaron utilizando clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))macOS Sierra en una MacBook Pro Mid 2012.

Editar: También probé el rendimiento en una máquina Linux usando gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. En esta plataforma, el beneficio de rendimiento no fue tan significativo como en macOs con clang.

Salida (sin optimización del compilador):

InstanceOf performance(A->D)  : 390768 us
InstanceOf performance(A->C)  : 333994 us
InstanceOf performance(A->B)  : 334596 us
InstanceOf performance(A->A)  : 300959 us

DynamicCast performance(A->D) : 331942 us
DynamicCast performance(A->C) : 303715 us
DynamicCast performance(A->B) : 400262 us
DynamicCast performance(A->A) : 324942 us

Salida (con optimización del compilador):

InstanceOf performance(A->D)  : 209501 us
InstanceOf performance(A->C)  : 208727 us
InstanceOf performance(A->B)  : 207815 us
InstanceOf performance(A->A)  : 197953 us

DynamicCast performance(A->D) : 259417 us
DynamicCast performance(A->C) : 256203 us
DynamicCast performance(A->B) : 261202 us
DynamicCast performance(A->A) : 193535 us
andi1337
fuente
¡Respuesta bien pensada! Me alegra que hayas proporcionado los horarios. Esta fue una lectura interesante.
Eric
0

dynamic_castSe sabe que es ineficiente. Atraviesa la jerarquía de herencia, y es la única solución si tiene múltiples niveles de herencia, y necesita verificar si un objeto es una instancia de cualquiera de los tipos en su jerarquía de tipos.

Pero si una forma más limitada de instanceofeso solo verifica si un objeto es exactamente del tipo que especifique, es suficiente para sus necesidades, la función a continuación sería mucho más eficiente:

template<typename T, typename K>
inline bool isType(const K &k) {
    return typeid(T).hash_code() == typeid(k).hash_code();
}

Aquí hay un ejemplo de cómo invocaría la función anterior:

DerivedA k;
Base *p = &k;

cout << boolalpha << isType<DerivedA>(*p) << endl;  // true
cout << boolalpha << isType<DerivedB>(*p) << endl;  // false

Debería especificar el tipo de plantilla A(como el tipo que está buscando) y pasar el objeto que desea probar como argumento (a partir del cual Kse deduciría el tipo de plantilla ).

Arjun Menon
fuente
El estándar no requiere que hash_code sea único para diferentes tipos, por lo que esto no es confiable.
mattnz
2
¿No es typeid (T) en sí comparable con la igualdad, por lo que no se necesita confiar en el código hash?
Paul Stelian
-5
#include <iostream.h>
#include<typeinfo.h>

template<class T>
void fun(T a)
{
  if(typeid(T) == typeid(int))
  {
     //Do something
     cout<<"int";
  }
  else if(typeid(T) == typeid(float))
  {
     //Do Something else
     cout<<"float";
  }
}

void main()
 {
      fun(23);
      fun(90.67f);
 }
HHH
fuente
1
Este es un muy mal ejemplo. ¿Por qué no usar sobrecarga, eso es más barato?
user1095108
11
El principal problema es que no responde la pregunta. instanceofconsulta el tipo dinámico, pero en esta respuesta el tipo dinámico y estático siempre se corresponden.
MSalters
¡@HHH tu respuesta está muy lejos de la pregunta que se hace!
programador el
-11

Esto funcionó perfecto para mí usando Code :: Blocks IDE con GCC complier

#include<iostream>
#include<typeinfo>
#include<iomanip>
#define SIZE 20
using namespace std;

class Publication
{
protected:
    char title[SIZE];
    int price;

public:
    Publication()
    {
        cout<<endl<<" Enter title of media : ";
        cin>>title;

        cout<<endl<<" Enter price of media : ";
        cin>>price;
    }

    virtual void show()=0;
};

class Book : public Publication
{
    int pages;

public:
    Book()
    {
        cout<<endl<<" Enter number of pages : ";
        cin>>pages;
    }

    void show()
    {
        cout<<endl<<setw(12)<<left<<" Book Title"<<": "<<title;
        cout<<endl<<setw(12)<<left<<" Price"<<": "<<price;
        cout<<endl<<setw(12)<<left<<" Pages"<<": "<<pages;
        cout<<endl<<" ----------------------------------------";
    }
};

class Tape : public Publication
{
    int duration;

public:
    Tape()
    {
        cout<<endl<<" Enter duration in minute : ";
        cin>>duration;
    }

    void show()
    {
        cout<<endl<<setw(10)<<left<<" Tape Title"<<": "<<title;
        cout<<endl<<setw(10)<<left<<" Price"<<": "<<price;
        cout<<endl<<setw(10)<<left<<" Duration"<<": "<<duration<<" minutes";
        cout<<endl<<" ----------------------------------------";
    }
};
int main()
{
    int n, i, type;

    cout<<endl<<" Enter number of media : ";
    cin>>n;

    Publication **p = new Publication*[n];
    cout<<endl<<" Enter "<<n<<" media details : ";

    for(i=0;i<n;i++)
    {
        cout<<endl<<" Select Media Type [ 1 - Book / 2 - Tape ] ";
        cin>>type;

        if ( type == 1 )
        {
            p[i] = new Book();
        }
        else
        if ( type == 2 )
        {
            p[i] = new Tape();
        }
        else
        {
            i--;
            cout<<endl<<" Invalid type. You have to Re-enter choice";
        }
    }

    for(i=0;i<n;i++)
    {
        if ( typeid(Book) == typeid(*p[i]) )
        {
            p[i]->show();
        }
    }

    return 0;
}
pgp
fuente
1
@programmer Creo que quieres decir @pgp, simplemente arreglé el formato de su código. Además, su respuesta parece ser básicamente "uso typeid", que si bien es incorrecta ("No hay garantía de que todas las evaluaciones de la expresión typeid en el mismo tipo ... assert(typeid(A) == typeid(A)); /* not guaranteed */" hagan referencia a la misma instancia std :: type_info ), vea cppreference.com ), indica que al menos trató de responder la pregunta, si no fue útil porque se olvidó de ofrecer un ejemplo de trabajo mínimo.
Andres Riofrio