Odpowiednik C ++ instancji Java

202

Jaka jest preferowana metoda uzyskania odpowiednika języka Java w języku C ++ instanceof?

Yuval Adam
źródło
57
Preferowane przez wydajność i zgodność ...
Yuval Adam
7
czy nie jest fair zapytać „instanceof - w jakim języku?”
mysticcoder,
3
@mysticcoder: Dostaję „например на” dla bułgarskiego, GT jednak nie obsługuje C ++
Mark K Cowan,

Odpowiedzi:

200

Spróbuj użyć:

if(NewType* v = dynamic_cast<NewType*>(old)) {
   // old was safely casted to NewType
   v->doSomething();
}

Wymaga to włączenia w kompilatorze obsługi rtti.

EDYCJA: Mam kilka dobrych komentarzy na temat tej odpowiedzi!

Za każdym razem, gdy potrzebujesz użyć dynamicznej transmisji (lub instanceof), lepiej zadaj sobie pytanie, czy jest to konieczne. Zazwyczaj jest to oznaka złego projektu.

Typowe obejścia tego problemu polegają na umieszczeniu specjalnego zachowania dla klasy, którą sprawdzasz, w funkcji wirtualnej w klasie bazowej lub być może wprowadzeniu czegoś takiego jak gość, w którym możesz wprowadzić określone zachowanie dla podklas bez zmiany interfejsu (z wyjątkiem dodania interfejsu akceptacji przez kierunek).

Jak wskazano, dynamic_cast nie jest darmowy. Prostym i konsekwentnie działającym hackem, który obsługuje większość (ale nie wszystkie przypadki), jest w zasadzie dodanie wyliczenia reprezentującego wszystkie możliwe typy, jakie może mieć Twoja klasa i sprawdzenie, czy masz właściwy.

if(old->getType() == BOX) {
   Box* box = static_cast<Box*>(old);
   // Do something box specific
}

To nie jest dobry projekt z oo, ale może być obejściem, a jego koszt to mniej więcej wirtualne wywołanie funkcji. Działa również niezależnie od tego, czy RTTI jest włączony czy nie.

Pamiętaj, że to podejście nie obsługuje wielu poziomów dziedziczenia, więc jeśli nie będziesz ostrożny, możesz skończyć z kodem wyglądającym tak:

// 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
}
Laserallan
źródło
4
Zwykle dzieje się tak, gdy wykonujesz kontrolę „instanceof”
Laserallan
7
Jeśli musisz użyć instanceof, w większości przypadków coś jest nie tak z twoim projektem.
mslot
24
Nie zapominaj, że dynamic_cast to operacja o dużych kosztach.
Klaim
13
Istnieje wiele przykładów racjonalnych zastosowań dynamicznego testowania typu. Zwykle nie jest to preferowane, ale ma swoje miejsce. (W przeciwnym razie, dlaczego miałby lub jego odpowiednik pojawiać się w każdym większym języku OO: C ++, Java, Python itp.?)
Paul Draper,
2
Złapałbym oba na poziomie IOException, gdyby nie trzeba było traktować ich inaczej. Jeśli trzeba je obsługiwać w różny sposób, dodam blok catch dla każdego wyjątku.
mslot,
37

W zależności od tego, co chcesz zrobić, możesz to zrobić:

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

Posługiwać się:

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

Działa to jednak wyłącznie na typach znanych kompilatorowi.

Edytować:

Ten kod powinien działać dla wskaźników polimorficznych:

template<typename Base, typename T>
inline bool instanceof(const T *ptr) {
    return dynamic_cast<const Base*>(ptr) != nullptr;
}

Przykład: http://cpp.sh/6qir

panzi
źródło
Eleganckie i dobrze wykonane rozwiązanie. +1 Ale uważaj, aby uzyskać prawidłowy wskaźnik. Nie dotyczy wskaźnika polimorficznego?
Adrian Maire,
Co się stanie, jeśli podczas korzystania z tej funkcji nie rozpoznamy wskaźnika? Czy działałoby to w przypadku wskaźników polimorficznych?
mark.kedzierski
Nie, działa to tylko na typach znanych kompilatorowi. Nie będzie działać ze wskaźnikami polimorficznymi, bez względu na to, czy derefernece, czy nie. Dodam jednak coś, co może w tym przypadku zadziałać.
panzi
2
Zmodyfikowałem twój przykład, aby napisać wersję tej metody, która używa referencji zamiast wskaźników: cpp.sh/8owv
Sri Harsha Chilakapati
Dlaczego typ docelowy rzutowania dynamicznego „const”?
user1056903
7

Instancja implementacji bez Dynamic_cast

Myślę, że to pytanie jest nadal aktualne. Korzystając ze standardu C ++ 11, możesz teraz zaimplementować instanceoffunkcję bez użycia dynamic_casttakiego:

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

Ale nadal jesteś zależny od RTTIwsparcia. Oto moje rozwiązanie tego problemu w zależności od niektórych makr i magii metaprogramowania. Jedyną wadą jest to, że takie podejście nie działa w przypadku wielokrotnego dziedziczenia .

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

Próbny

Możesz następnie użyć tych rzeczy ( ostrożnie ) w następujący sposób:

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)

Poniższy kod przedstawia małe demo w celu zweryfikowania szczątkowego prawidłowego zachowania.

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

Wynik:

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

Występ

Najciekawsze pytanie, jakie się teraz pojawia, brzmi: czy te złe rzeczy są bardziej wydajne niż ich użycie dynamic_cast. Dlatego napisałem bardzo podstawową aplikację do pomiaru wydajności.

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

Wyniki różnią się i są zasadniczo oparte na stopniu optymalizacji kompilatora. Kompilowanie programu pomiaru wydajności przy użyciu g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppdanych wyjściowych na moim komputerze lokalnym było:

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, ten wynik był bardzo otrzeźwiający, ponieważ czasy pokazują, że nowe podejście nie jest dużo szybsze w porównaniu do dynamic_castpodejścia. Jest nawet mniej wydajny dla specjalnego przypadku testowego, który sprawdza, czy wskaźnik Ajest instancją A. ALE fala się zmienia, dostrajając nasz plik binarny za pomocą optymalizacji kompilatora. Odpowiednie polecenie kompilatora to g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. Wynik na mojej lokalnej maszynie był niesamowity:

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

Jeśli nie jesteś zależny od wielokrotnego dziedziczenia, nie jesteś przeciwnikiem starych dobrych makr C, RTTI i metaprogramowania szablonów i nie jesteś zbyt leniwy, aby dodać kilka małych instrukcji do klas hierarchii klas, to takie podejście może nieco poprawić twoją aplikację jeśli chodzi o jego wydajność, jeśli często kończy się na sprawdzeniu wystąpienia wskaźnika. Ale używaj go ostrożnie . Nie ma gwarancji na poprawność tego podejścia.

Uwaga: Wszystkie demonstracje zostały skompilowane przy użyciu clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))MacOS Sierra na MacBooku Pro w połowie 2012 roku.

Edycja: Testowałem również wydajność na komputerze z systemem Linux przy użyciu gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. Na tej platformie korzyść związana z wydajnością nie była tak znacząca, jak w przypadku MacO z clang.

Dane wyjściowe (bez optymalizacji kompilatora):

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

Dane wyjściowe (z optymalizacją kompilatora):

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
andi1337
źródło
Dobrze przemyślana odpowiedź! Cieszę się, że podałeś czasy. To była ciekawa lektura.
Eric
0

dynamic_castwiadomo, że jest nieefektywny. Przechodzi przez hierarchię dziedziczenia i jest to jedyne rozwiązanie, jeśli masz wiele poziomów dziedziczenia i musisz sprawdzić, czy obiekt jest instancją dowolnego z typów w swojej hierarchii typów.

Ale jeśli bardziej ograniczona forma instanceoftego sprawdza tylko, czy obiekt jest dokładnie taki, jaki określisz, wystarczy dla twoich potrzeb, funkcja poniżej byłaby o wiele bardziej wydajna:

template<typename T, typename K>
inline bool isType(const K &k) {
    return typeid(T).hash_code() == typeid(k).hash_code();
}

Oto przykład wywołania powyższej funkcji:

DerivedA k;
Base *p = &k;

cout << boolalpha << isType<DerivedA>(*p) << endl;  // true
cout << boolalpha << isType<DerivedB>(*p) << endl;  // false

Podaj typ szablonu A(jako typ, którego szukasz) i przekaż obiekt, który chcesz przetestować, jako argument (z którego typu szablonu Kbyłby wywnioskowany).

Arjun Menon
źródło
Standard nie wymaga, aby hash_code był unikalny dla różnych typów, więc jest to niewiarygodne.
mattnz
2
Czy maszynopis (T) sam w sobie nie jest porównywalny z równością, więc nie jest konieczne poleganie na haszyszu?
Paul Stelian,
-5
#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);
 }
HHH
źródło
1
To naprawdę zły przykład. Dlaczego nie skorzystać z przeciążenia, które jest tańsze?
user1095108
11
Głównym problemem jest to, że nie odpowiada na pytanie. instanceofodpytuje typ dynamiczny, ale w tej odpowiedzi typ dynamiczny i statyczny zawsze odpowiadają.
MSalters
@ HHH, na który odpowiadasz jest daleki od zadanego pytania!
programista
-11

Działa to idealnie dla mnie przy użyciu Code :: Blocks IDE z kompilatorem GCC

#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;
}
pgp
źródło
1
@programmer Myślę, że chcesz wywołać @pgp, po prostu naprawiłem jego formatowanie kodu. Ponadto wydaje się, że jego odpowiedź brzmi „use typeid”, co wprawdzie jest błędne („Nie ma gwarancji, że do tej samej instancji std :: type_info przywołane zostaną wszystkie oceny wyrażenia typeid tego samego typu ... assert(typeid(A) == typeid(A)); /* not guaranteed */”, patrz cppreference.com ), wskazuje, że przynajmniej próbował odpowiedzieć na pytanie, choć bezskutecznie, ponieważ zaniedbał zaoferowania minimalnego działającego przykładu.
Andres Riofrio