Quelle est la méthode à privilégier pour obtenir l'équivalent en C++ de la méthode java instanceof
?
La classe doit avoir au moins une méthode virtuelle pour que cela fonctionne.
Quelle est la méthode à privilégier pour obtenir l'équivalent en C++ de la méthode java instanceof
?
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
}
Si vous devez utiliser instanceof, il y a, dans la plupart des cas, quelque chose qui ne va pas dans votre conception.
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
Solution élégante et bien faite. +1 Mais attention à obtenir le bon pointeur. Non valide pour les pointeurs polymorphes ?
Et si nous déréférencions le pointeur lors de l'utilisation de cette fonction ? Cela fonctionnerait-il alors pour les pointeurs polymorphes ?
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.
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());
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
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
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).
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.
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.
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++.