it-swarm.com.de

C ++ - Äquivalent von instanceof

Was ist die bevorzugte Methode, um das C++ - Äquivalent von instanceof zu erreichen?

193
Yuval Adam

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 hatte einige gute Kommentare zu dieser Antwort!

Jedes Mal, wenn Sie einen dynamic_cast (oder eine Instanz davon) verwenden müssen, sollten Sie sich fragen, ob dies eine notwendige Sache ist. Es ist im Allgemeinen ein Zeichen für schlechtes Design.

Eine typische Problemumgehung besteht darin, das spezielle Verhalten für die Klasse, für die Sie einchecken, in eine virtuelle Funktion für die Basisklasse zu integrieren oder beispielsweise ein Besucher einzufügen, mit dem Sie ein bestimmtes Verhalten für Unterklassen festlegen können, ohne die Schnittstelle zu ändern (außer zum Hinzufügen der Besucherakzeptanz-Schnittstelle natürlich).

Wie bereits erwähnt, ist dynamic_cast nicht kostenlos. Ein einfacher und konsistent durchgeführter Hack, der die meisten (aber nicht alle Fälle) behandelt, fügt im Grunde genommen eine Aufzählung hinzu, die alle möglichen Typen darstellt, die Ihre Klasse haben kann, und prüft, 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 ein Workaround 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, endet der Code möglicherweise folgendermaßen:

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

Je nachdem, was Sie tun möchten, können Sie dies 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 mit den Typen, die dem Compiler bekannt sind.

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

33
panzi

Implementierungsinstanz ohne dynamic_cast

Ich denke, diese Frage ist bis heute aktuell. Mit dem C++ 11-Standard können Sie jetzt eine instanceof -Funktion implementieren, ohne dynamic_cast Wie folgt zu verwenden:

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

Sie sind jedoch weiterhin auf die Unterstützung von RTTI angewiesen. Hier ist meine Lösung für dieses Problem, abhängig von einigen Makros und Metaprogramming Magic. Der einzige Nachteil imho ist, dass dieser Ansatz funktioniert nicht für Mehrfachvererbung .

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

Demo

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 zeigt eine kleine Demo, um das korrekte Verhalten ansatzweise 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

Performance

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. Beim Kompilieren des Leistungsmessprogramms mit g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cpp Wurde auf meinem lokalen Computer Folgendes ausgegeben:

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, denn die Zeitangaben zeigen, dass der neue Ansatz im Vergleich zum dynamic_cast - Ansatz nicht viel schneller ist. Noch weniger effizient ist es für den speziellen Testfall, bei dem geprüft wird, ob ein Zeiger von A eine Instanz von A ist. ABER das Blatt dreht sich, indem wir unsere Binärdatei mit Hilfe der Compiler-Optimierung optimieren. Der entsprechende Compiler-Befehl 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, keine Gegner der guten alten C-Makros, RTTI- und Template-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 am Ende oft die Instanz eines Zeigers überprüfen. Aber mit Vorsicht. Es gibt keine Garantie für die Richtigkeit dieses Ansatzes.

Hinweis: Alle Demos wurden mit clang (Apple LLVM version 9.0.0 (clang-900.0.39.2)) unter macOS Sierra auf einem MacBook Pro Mid 2012 kompiliert.

Edit: Ich habe die Leistung auch auf einem Linux-Computer mit gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 getestet. Auf dieser Plattform war der Leistungsvorteil nicht so signifikant wie bei MacOs mit Klirren.

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
3
andi1337

dynamic_cast ist als ineffizient bekannt. Es durchläuft die Vererbungshierarchie und es 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 von instanceof, die nur prüft, ob ein Objekt genau dem von Ihnen angegebenen Typ entspricht, für Ihre Anforderungen ausreicht, wäre die folgende Funktion wesentlich 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 A (als Typ, auf den Sie prüfen) an und übergeben das zu testende Objekt als Argument (aus dem auf den Vorlagentyp K geschlossen wird). .

0
Arjun Menon