Was ist die bevorzugte Methode, um das C ++ - Äquivalent von Java zu erreichen instanceof
?
Was ist die bevorzugte Methode, um das C ++ - Äquivalent von Java zu erreichen instanceof
?
Antworten:
Versuchen Sie es mit:
if(NewType* v = dynamic_cast<NewType*>(old)) {
// old was safely casted to NewType
v->doSomething();
}
Dies setzt voraus, dass Ihr Compiler die Rtti-Unterstützung aktiviert hat.
EDIT: Ich habe einige gute Kommentare zu dieser Antwort gehabt!
Jedes Mal, wenn Sie einen dynamic_cast (oder eine Instanz davon) verwenden müssen, sollten Sie sich fragen, ob dies erforderlich ist. Es ist im Allgemeinen ein Zeichen für schlechtes Design.
Typische Problemumgehungen bestehen darin, das spezielle Verhalten für die Klasse, nach der Sie suchen, in eine virtuelle Funktion in der Basisklasse zu integrieren oder möglicherweise etwas wie einen Besucher einzuführen, in dem Sie ein bestimmtes Verhalten für Unterklassen einführen können, ohne die Schnittstelle zu ändern (außer das Hinzufügen der Besucherakzeptanzschnittstelle von Kurs).
Wie bereits erwähnt, ist dynamic_cast nicht kostenlos. Ein einfacher und konsistent ausführender Hack, der die meisten (aber nicht alle Fälle) behandelt, besteht im Grunde darin, eine Aufzählung hinzuzufügen, die alle möglichen Typen Ihrer Klasse darstellt, und zu prüfen, ob Sie den richtigen haben.
if(old->getType() == BOX) {
Box* box = static_cast<Box*>(old);
// Do something box specific
}
Dies ist kein gutes Design, aber es kann eine Problemumgehung sein und seine Kosten sind mehr oder weniger nur ein virtueller Funktionsaufruf. Es funktioniert auch unabhängig davon, ob RTTI aktiviert ist oder nicht.
Beachten Sie, dass dieser Ansatz nicht mehrere Vererbungsebenen unterstützt. Wenn Sie also nicht aufpassen, können Sie mit Code enden, der wie folgt aussieht:
// 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
}
Je nachdem, was Sie tun möchten, können Sie Folgendes tun:
template<typename Base, typename T>
inline bool instanceof(const T*) {
return std::is_base_of<Base, T>::value;
}
Verwenden:
if (instanceof<BaseClass>(ptr)) { ... }
Dies funktioniert jedoch nur bei den dem Compiler bekannten Typen.
Bearbeiten:
Dieser Code sollte für polymorphe Zeiger funktionieren:
template<typename Base, typename T>
inline bool instanceof(const T *ptr) {
return dynamic_cast<const Base*>(ptr) != nullptr;
}
Beispiel: http://cpp.sh/6qir
Ich denke, diese Frage ist heute noch relevant. Mit dem C ++ 11-Standard können Sie jetzt eine instanceof
Funktion implementieren , ohne Folgendes zu verwenden dynamic_cast
:
if (dynamic_cast<B*>(aPtr) != nullptr) {
// aPtr is instance of B
} else {
// aPtr is NOT instance of B
}
Aber Sie sind immer noch auf RTTI
Unterstützung angewiesen . Hier ist meine Lösung für dieses Problem, abhängig von einigen Makros und Metaprogrammiermagie. Der einzige Nachteil imho ist, dass dieser Ansatz nicht für Mehrfachvererbung funktioniert .
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());
Sie können dieses Zeug dann ( mit Vorsicht ) wie folgt verwenden:
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)
Der folgende Code enthält eine kleine Demo, um das korrekte Verhalten rudimentär zu überprüfen.
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;
}
Ausgabe:
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
Die interessanteste Frage, die sich jetzt stellt, ist, ob dieses böse Zeug effizienter ist als die Verwendung von dynamic_cast
. Deshalb habe ich eine sehr einfache App zur Leistungsmessung geschrieben.
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;
}
Die Ergebnisse variieren und basieren im Wesentlichen auf dem Grad der Compileroptimierung. Das Kompilieren des Leistungsmessprogramms unter Verwendung g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cpp
der Ausgabe auf meinem lokalen Computer war:
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, dieses Ergebnis war sehr ernüchternd, da das Timing zeigt, dass der neue Ansatz im Vergleich zum dynamic_cast
Ansatz nicht viel schneller ist . Es ist noch weniger effizient für den speziellen Testfall, der prüft, ob ein Zeiger von A
eine Instanz von ist A
. ABER das Blatt wendet sich, indem wir unsere Binärdatei mithilfe der Compiler-Otpimierung optimieren. Der jeweilige Compilerbefehl lautet g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp
. Das Ergebnis auf meinem lokalen Computer war erstaunlich:
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
Wenn Sie nicht auf Mehrfachvererbung angewiesen sind, kein Gegner guter alter C-Makros, RTTI- und Vorlagen-Metaprogrammierung sind und nicht zu faul sind, um den Klassen Ihrer Klassenhierarchie einige kleine Anweisungen hinzuzufügen, kann dieser Ansatz Ihre Anwendung ein wenig verbessern in Bezug auf seine Leistung, wenn Sie häufig die Instanz eines Zeigers überprüfen. Aber verwenden Sie es mit Vorsicht . Es gibt keine Garantie für die Richtigkeit dieses Ansatzes.
Hinweis: Alle Demos wurden clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))
unter macOS Sierra auf einem MacBook Pro Mid 2012 kompiliert .
Bearbeiten:
Ich habe auch die Leistung auf einem Linux-Computer mit getestet gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
. Auf dieser Plattform war der Leistungsvorteil nicht so signifikant wie auf MacOs mit Clang.
Ausgabe (ohne Compileroptimierung):
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
Ausgabe (mit Compileroptimierung):
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
ist als ineffizient bekannt. Es durchläuft die Vererbungshierarchie und ist die einzige Lösung, wenn Sie über mehrere Vererbungsebenen verfügen und prüfen müssen, ob ein Objekt eine Instanz eines der Typen in seiner Typhierarchie ist.
Wenn jedoch eine eingeschränktere Form instanceof
davon nur prüft, ob ein Objekt genau dem von Ihnen angegebenen Typ entspricht, der für Ihre Anforderungen ausreicht, ist die folgende Funktion viel effizienter:
template<typename T, typename K>
inline bool isType(const K &k) {
return typeid(T).hash_code() == typeid(k).hash_code();
}
Hier ist ein Beispiel, wie Sie die obige Funktion aufrufen würden:
DerivedA k;
Base *p = &k;
cout << boolalpha << isType<DerivedA>(*p) << endl; // true
cout << boolalpha << isType<DerivedB>(*p) << endl; // false
Sie geben den Vorlagentyp an A
(als den Typ, nach dem Sie suchen) und übergeben das zu testende Objekt als Argument (aus dem der Vorlagentyp K
abgeleitet wird).
#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
fragt den dynamischen Typ ab, aber in dieser Antwort stimmen der dynamische und der statische Typ immer überein.
Dies funktionierte perfekt für mich mit Code :: Blocks IDE mit GCC-Konformität
#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
" zu sein, was zwar falsch ist ("Es gibt keine Garantie dafür, dass bei allen Auswertungen des typeid-Ausdrucks auf demselben Typ auf dieselbe std :: type_info-Instanz verwiesen wird ... assert(typeid(A) == typeid(A)); /* not guaranteed */
", siehe cppreference.com ) gibt an, dass er zumindest versucht hat, die Frage zu beantworten, wenn auch nicht hilfreich, weil er es versäumt hat, ein minimales Arbeitsbeispiel anzubieten.