Quelle est la méthode préférée pour obtenir l'équivalent C ++ de Java instanceof
?
Quelle est la méthode préférée pour obtenir l'équivalent C ++ de Java instanceof
?
Réponses:
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
}
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
Je pense que cette question est toujours d'actualité. En utilisant la norme C ++ 11, vous pouvez désormais implémenter une instanceof
fonction sans utiliser dynamic_cast
comme 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 RTTI
support. 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());
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
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.cpp
l' 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_cast
approche. Il est encore moins efficace pour le cas de test spécial qui teste si un pointeur de A
est 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
dynamic_cast
est 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 instanceof
cela 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 K
serait déduit).
#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);
}
instanceof
interroge le type dynamique, mais dans cette réponse, le type dynamique et statique correspondent toujours.
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;
}
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.