238 votes

L'équivalent en C++ de l'instanceof de Java.

Quelle est la méthode à privilégier pour obtenir l'équivalent en C++ de la méthode java instanceof ?

59 votes

Préféré par la performance et la compatibilité...

7 votes

N'est-il pas juste de demander "instanceof - dans quelle langue ?"

6 votes

@mysticcoder : J'obtiens " " pour le bulgare, mais GT ne supporte pas le C++.

223voto

Laserallan Points 5500

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 le support rtti activé.

EDIT : J'ai reçu 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 le signe d'une mauvaise conception.

Les solutions de contournement typiques consistent à placer le comportement spécial pour la classe que vous vérifiez dans une fonction virtuelle de 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 modifier l'interface (sauf pour ajouter l'interface d'acceptation des visiteurs bien sûr).

Comme indiqué, la diffusion dynamique n'est pas gratuite. Un hack simple et efficace qui gère la plupart des cas (mais pas tous) consiste à ajouter un enum représentant tous les types possibles de votre classe et à vérifier si vous avez le bon type.

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 est plus ou moins un simple appel de fonction virtuelle. Cela fonctionne également que le RTTI soit activé ou non.

Notez que cette approche ne prend pas en charge les niveaux d'héritage multiples. Si vous ne faites pas attention, vous risquez de vous retrouver avec un code ressemblant à celui-ci :

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

2 votes

La classe doit avoir au moins une méthode virtuelle pour que cela fonctionne.

7 votes

C'est généralement le cas lorsque vous faites une vérification de type "instanceof".

8 votes

Si vous devez utiliser instanceof, il y a, dans la plupart des cas, quelque chose qui ne va pas dans votre conception.

49voto

panzi Points 2061

Selon ce que vous voulez faire, vous pourriez faire ceci :

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

Utilisez :

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

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

Editar:

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

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

0 votes

Solution élégante et bien faite. +1 Mais attention à obtenir le bon pointeur. Non valide pour les pointeurs polymorphes ?

0 votes

Et si nous déréférencions le pointeur lors de l'utilisation de cette fonction ? Cela fonctionnerait-il alors pour les pointeurs polymorphes ?

0 votes

Non, cela ne fonctionne que sur les types tels que connus par le compilateur. Cela ne fonctionnera pas avec les pointeurs polymorphes, peu importe que vous déréférenciez ou non. Je vais ajouter quelque chose qui pourrait fonctionner dans ce cas.

15voto

andi1337 Points 151

Instance de l'implémentation sans dynamic_cast

Je pense que cette question est toujours d'actualité. En utilisant la norme C++11, vous êtes maintenant en mesure d'implémenter un programme de type instanceof sans utiliser la fonction dynamic_cast comme ça :

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

Mais vous dépendez toujours RTTI soutien. Voici donc ma solution à ce problème, qui repose sur des macros et la magie de la métaprogrammation. Le seul inconvénient, à mon avis, c'est que cette approche ne permet pas d'atteindre les objectifs suivants pas travailler pour 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* (en anglais)

#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émo pour vérifier de façon rudimentaire 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;
}

Sortie :

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 cette substance maléfique est plus efficace que l'utilisation de dynamic_cast . C'est pourquoi j'ai écrit une application très basique de mesure des performances.

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 en utilisant g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cpp 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 donne à réfléchir, car les chronologies démontrent que la nouvelle approche n'est pas beaucoup plus rapide par rapport à l'approche de la dynamic_cast approche. Elle est encore moins efficace pour le cas de test spécial qui teste si un pointeur de type A est une instance de A . MAIS le vent tourne en réglant notre binaire à l'aide de l'optimisation du compilateur. La commande respective du compilateur est g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp . Le résultat sur ma machine locale était étonnant :

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, si vous n'êtes pas un adversaire des bonnes vieilles macros C, du RTTI et de la métaprogrammation par gabarit et si vous n'êtes pas trop paresseux pour ajouter quelques petites instructions aux classes de votre hiérarchie de classes, alors cette approche peut améliorer un peu les performances de votre application, si vous vous retrouvez souvent à vérifier l'instance d'un pointeur. Mais utilisez-le avec prudence . Il n'y a aucune garantie quant à l'exactitude de cette approche.

Note : Toutes les démos ont été compilées en utilisant clang (Apple LLVM version 9.0.0 (clang-900.0.39.2)) sous macOS Sierra sur un MacBook Pro Mid 2012.

Editar: 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, le gain de performance n'est pas aussi important que sur 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

1 votes

Réponse bien réfléchie ! Je suis heureux que vous ayez fourni les horaires. C'était une lecture intéressante.

2voto

Arjun Menon Points 100

dynamic_cast est connu pour être inefficace. Il parcourt la hiérarchie de l'héritage, et c'est la seule solution si vous avez plusieurs niveaux d'héritage, et que vous 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 instanceof qui vérifie seulement si un objet est exactement du type que vous spécifiez, suffit pour 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 d'invoquer 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 (en tant que type à vérifier), et passez l'objet que vous voulez tester en tant qu'argument (à partir duquel le modèle de type K serait déduite).

0 votes

La norme n'exige pas que le hash_code soit unique pour les différents types, ce qui n'est donc pas fiable.

3 votes

Le typeid(T) n'est-il pas lui-même comparable à l'égalité, de sorte qu'il n'est pas nécessaire de s'appuyer sur le code de hachage ?

-3voto

HHH Points 39
#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);
 }

1 votes

C'est un très mauvais exemple. Pourquoi ne pas utiliser la surcharge, qui est moins chère ?

11 votes

Le principal problème est qu'il ne répond pas à la question. instanceof interroge le type dynamique, mais dans cette réponse, les types dynamique et statique correspondent toujours.

0 votes

@HHH votre réponse est très éloignée de la question posée !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X