Équivalent C ++ de l'instanceof de Java

202

Quelle est la méthode préférée pour obtenir l'équivalent C ++ de Java instanceof?

Yuval Adam
la source
57
Préféré par la performance et la compatibilité ...
Yuval Adam
7
n'est-il pas juste de demander "instanceof - dans quelle langue?"
mysticcoder
3
@mysticcoder: Je reçois "например на" pour le bulgare, GT ne prend pas en charge le C ++ cependant
Mark K Cowan

Réponses:

200

Essayez d'utiliser:

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

Cela nécessite que votre compilateur ait activé le support rtti.

EDIT: J'ai eu de bons commentaires sur cette réponse!

Chaque fois que vous devez utiliser un dynamic_cast (ou instanceof), vous feriez mieux de vous demander si c'est une chose nécessaire. C'est généralement un signe de mauvaise conception.

Les solutions de contournement typiques consistent à placer le comportement spécial de la classe que vous recherchez dans une fonction virtuelle sur la classe de base ou peut-être à introduire quelque chose comme un visiteur où vous pouvez introduire un comportement spécifique pour les sous-classes sans changer l'interface (sauf pour ajouter l'interface d'acceptation des visiteurs de cours).

Comme souligné, dynamic_cast n'est pas gratuit. Un hack simple et performant qui gère la plupart (mais pas tous les cas) consiste essentiellement à ajouter une énumération représentant tous les types possibles que votre classe peut avoir et à vérifier si vous avez le bon.

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

Ce n'est pas une bonne conception, mais cela peut être une solution de contournement et son coût n'est plus ou moins qu'un appel de fonction virtuelle. Il fonctionne également indépendamment du fait que RTTI soit activé ou non.

Notez que cette approche ne prend pas en charge plusieurs niveaux d'héritage, donc si vous ne faites pas attention, vous pourriez vous retrouver avec du code ressemblant à ceci:

// 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
la source
4
C'est généralement le cas lorsque vous effectuez une vérification "instanceof"
Laserallan
7
Si vous devez utiliser instanceof, il y a, dans la plupart des cas, un problème avec votre conception.
mslot
24
N'oubliez pas que dynamic_cast est une opération coûteuse.
Klaim
13
Il existe de nombreux exemples d'utilisations raisonnables des tests de type dynamique. Ce n'est généralement pas préféré, mais il a sa place. (Sinon, pourquoi cela ou son équivalent apparaîtrait-il dans tous les principaux langages OO: C ++, Java, Python, etc.?)
Paul Draper
2
J'attraperais les deux au niveau IOException s'ils n'ont pas besoin d'être traités différemment. S'ils doivent être traités différemment, j'ajouterais un bloc catch pour chaque exception.
mslot
37

Selon ce que vous voulez faire, vous pouvez le faire:

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

Utilisation:

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

Cependant, cela fonctionne uniquement sur les types connus par le compilateur.

Éditer:

Ce code devrait fonctionner pour les pointeurs polymorphes:

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

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

panzi
la source
Solution élégante et bien faite. +1 Mais faites attention à obtenir le bon pointeur. Pas valable pour le pointeur polymorphe?
Adrian Maire
Et si nous déférions le pointeur lors de l'utilisation de cette fonction? Cela fonctionnerait-il alors pour les pointeurs polymorphes?
mark.kedzierski
Non, cela ne fonctionne que sur les types connus du compilateur. Ne fonctionnera pas avec des pointeurs polymorphes, que vous déréferniez ou non. J'ajouterai cependant quelque chose qui pourrait fonctionner dans ce cas.
panzi
2
J'ai modifié votre exemple pour écrire une version de cette méthode qui utilise des références au lieu de pointeurs: cpp.sh/8owv
Sri Harsha Chilakapati
Pourquoi le type cible de la distribution dynamique "const"?
user1056903
7

Instance de l'implémentation sans dynamic_cast

Je pense que cette question est toujours d'actualité. En utilisant la norme C ++ 11, vous pouvez désormais implémenter une instanceoffonction sans utiliser dynamic_castcomme ceci:

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

Mais vous dépendez toujours du RTTIsupport. Voici donc ma solution à ce problème en fonction de certaines macros et de la métaprogrammation magique. Le seul inconvénient à mon humble avis est que cette approche ne fonctionne pas pour l' héritage multiple .

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());

Démo

Vous pouvez ensuite utiliser ce truc ( avec prudence ) comme suit:

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)

Le code suivant présente une petite démonstration pour vérifier rudimentairement le comportement correct.

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;
}

Production:

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

Performance

La question la plus intéressante qui se pose maintenant est de savoir si ce truc diabolique est plus efficace que l’utilisation de dynamic_cast . J'ai donc écrit une application de mesure des performances très basique.

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;
}

Les résultats varient et sont essentiellement basés sur le degré d'optimisation du compilateur. La compilation du programme de mesure des performances à g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppl' aide de la sortie sur ma machine locale était:

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, ce résultat était très décevant, car le timing démontre que la nouvelle approche n'est pas beaucoup plus rapide que l' dynamic_castapproche. Il est encore moins efficace pour le cas de test spécial qui teste si un pointeur de Aest une instance de A. MAIS la marée tourne en ajustant notre binaire à l'aide de l'optimisation du compilateur. La commande de compilation correspondante est g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. Le résultat sur ma machine locale était incroyable:

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 vous ne dépendez pas de l'héritage multiple, que vous n'êtes pas opposé aux bonnes vieilles macros C, au RTTI et à la métaprogrammation de modèles et que vous n'êtes pas trop paresseux pour ajouter de petites instructions aux classes de votre hiérarchie de classes, cette approche peut booster un peu votre application. en ce qui concerne ses performances, si vous finissez souvent par vérifier l'instance d'un pointeur. Mais utilisez-le avec prudence . Il n'y a aucune garantie quant à l'exactitude de cette approche.

Remarque: Toutes les démos ont été compilées à l'aide de clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))macOS Sierra sur un MacBook Pro mi-2012.

Edit: J'ai également testé les performances sur une machine Linux en utilisant gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. Sur cette plateforme, l'avantage de la performance n'était pas aussi important que sur les macOs avec clang.

Sortie (sans optimisation du compilateur):

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

Sortie (avec optimisation du compilateur):

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
la source
Réponse bien pensée! Je suis content que vous ayez fourni les horaires. Ce fut une lecture intéressante.
Eric
0

dynamic_castest connu pour être inefficace. Il traverse la hiérarchie d'héritage, et c'est la seule solution si vous avez plusieurs niveaux d'héritage et devez vérifier si un objet est une instance de l'un des types de sa hiérarchie de types.

Mais si une forme plus limitée de instanceofcela vérifie seulement si un objet est exactement le type que vous spécifiez, suffit à vos besoins, la fonction ci-dessous serait beaucoup plus efficace:

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

Voici un exemple de la façon dont vous appelleriez la fonction ci-dessus:

DerivedA k;
Base *p = &k;

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

Vous devez spécifier le type de modèle A(comme le type que vous recherchez) et passer l'objet que vous souhaitez tester comme argument (à partir duquel le type de modèle Kserait déduit).

Arjun Menon
la source
La norme n'exige pas que hash_code soit unique pour différents types, donc ce n'est pas fiable.
mattnz
2
Le typeid (T) n'est-il pas lui-même comparable à l'égalité, donc aucune dépendance au hashcode n'est nécessaire?
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
la source
1
Ceci est un très mauvais exemple. Pourquoi ne pas utiliser la surcharge, c'est moins cher?
user1095108
11
Le principal problème est qu'il ne répond pas à la question. instanceofinterroge le type dynamique, mais dans cette réponse, le type dynamique et statique correspondent toujours.
MSalters
@HHH vous répondez est bien loin de la question posée!
programmeur
-11

Cela a fonctionné parfaitement pour moi en utilisant Code :: Blocks IDE avec 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
la source
1
@programmer Je pense que vous voulez appeler @pgp, j'ai simplement corrigé la mise en forme de son code. De plus, sa réponse semble être fondamentalement "use typeid", ce qui, bien que faux ("Il n'y a aucune garantie que la même instance std :: type_info sera référencée par toutes les évaluations de l'expression typeid sur le même type ... assert(typeid(A) == typeid(A)); /* not guaranteed */", voir cppreference.com ), indique qu'il a au moins essayé de répondre à la question, mais sans aide, car il a négligé d'offrir un exemple de travail minimal.
Andres Riofrio