Equivalente in C ++ dell'istanza di java


202

Qual è il metodo preferito per ottenere l'equivalente in C ++ di Java instanceof?


57
Preferito dalle prestazioni e dalla compatibilità ...
Yuval Adam,

7
non è giusto chiedere "instanceof - in che lingua?"
mysticcoder il

3
@mysticcoder: ottengo "например на" per il bulgaro, tuttavia GT non supporta il C ++
Mark K Cowan,

Risposte:


200

Prova a usare:

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

Ciò richiede che il tuo compilatore abbia abilitato il supporto rtti.

EDIT: ho avuto dei buoni commenti su questa risposta!

Ogni volta che devi usare un Dynamic_cast (o istanza di), ti conviene chiederti se è una cosa necessaria. In genere è un segno di design scadente.

Soluzioni alternative tipiche sono l'inserimento del comportamento speciale per la classe che si sta verificando in una funzione virtuale sulla classe base o forse l'introduzione di qualcosa come un visitatore cui è possibile introdurre un comportamento specifico per le sottoclassi senza modificare l'interfaccia (tranne per l'aggiunta dell'interfaccia di accettazione del visitatore di corso).

Come sottolineato, dynamic_cast non è gratuito. Un hack semplice e con prestazioni costanti che gestisce la maggior parte (ma non tutti i casi) sta fondamentalmente aggiungendo un enum che rappresenta tutti i possibili tipi che la tua classe può avere e controlla se hai quello giusto.

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

Questo non è un buon design, ma può essere una soluzione alternativa e il suo costo è più o meno solo una chiamata di funzione virtuale. Funziona anche indipendentemente dal fatto che RTTI sia abilitato o meno.

Tieni presente che questo approccio non supporta più livelli di ereditarietà, quindi se non stai attento potresti finire con un codice simile al seguente:

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

4
Questo è generalmente il caso di un controllo "instanceof"
Laserallan,

7
Se devi usare instanceof, nella maggior parte dei casi c'è qualcosa di sbagliato nel tuo design.
mslot

24
Non dimenticare che dynamic_cast è un'operazione con un costo elevato.
Klaim,

13
Esistono molti esempi di usi ragionevoli delle prove dinamiche di tipo. Di solito non è preferito, ma ha un posto. (Altrimenti, perché dovrebbe comparire in tutti i principali linguaggi OO: C ++, Java, Python, ecc.?)
Paul Draper,

2
Li prenderei entrambi a livello di IOException se non dovessero essere gestiti diversamente. Se devono essere gestiti in modo diverso, aggiungerei un blocco catch per ogni eccezione.
mslot

37

A seconda di cosa vuoi fare, puoi farlo:

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

Uso:

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

Tuttavia, ciò funziona esclusivamente sui tipi conosciuti dal compilatore.

Modificare:

Questo codice dovrebbe funzionare per i puntatori polimorfici:

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

Esempio: http://cpp.sh/6qir


Soluzione elegante e ben fatta. +1 Ma fai attenzione a ottenere il puntatore corretto. Non valido per il puntatore polimorfico?
Adrian Maire,

Cosa succede se abbiamo differito il puntatore quando si utilizza questa funzione? Funzionerebbe quindi per i puntatori polimorfici?
mark.kedzierski,

No, questo funziona solo con i tipi noti al compilatore. Non funzionerà con i puntatori polimorfici, non importa se si derefernece o no. Aggiungerò qualcosa che potrebbe funzionare in quel caso però.
Panzi,

2
Ho modificato il tuo esempio per scrivere una versione di questo metodo che utilizza riferimenti anziché puntatori: cpp.sh/8owv
Sri Harsha Chilakapati,

Perché il tipo target del cast dinamico "const"?
user1056903

7

Istanza di implementazione senza dynamic_cast

Penso che questa domanda sia ancora rilevante oggi. Usando lo standard C ++ 11 ora puoi implementare una instanceoffunzione senza usare in dynamic_castquesto modo:

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

Ma fai ancora affidamento sul RTTIsupporto. Quindi ecco la mia soluzione per questo problema a seconda di alcune macro e Metaprogramming Magic. L'unico inconveniente è che questo approccio non funziona per l'ereditarietà multipla .

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

dimostrazione

È quindi possibile utilizzare questa roba ( con cautela ) come segue:

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)

Il codice seguente presenta una piccola demo per verificare rudimentalmente il comportamento corretto.

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

Produzione:

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

Prestazione

La domanda più interessante che si pone ora è se questa roba cattiva è più efficiente dell'uso di dynamic_cast . Pertanto ho scritto un'app di misurazione delle prestazioni molto semplice.

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

I risultati variano e si basano essenzialmente sul grado di ottimizzazione del compilatore. Compilare il programma di misurazione delle prestazioni utilizzando g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppl'output sul mio computer locale era:

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, questo risultato è stato molto rassicurante, perché i tempi dimostrano che il nuovo approccio non è molto più veloce rispetto dynamic_castall'approccio. È ancora meno efficiente per il caso di test speciale che verifica se un puntatore di Aè un'istanza di A. MA la marea gira sintonizzando il nostro binario usando l'ottimizzazione del compilatore. Il rispettivo comando del compilatore è g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. Il risultato sulla mia macchina locale è stato sorprendente:

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

Se non fai affidamento su ereditarietà multipla, non sei un avversario di buone vecchie macro C, RTTI e metaprogrammazione dei modelli e non sei troppo pigro per aggiungere alcune piccole istruzioni alle classi della tua gerarchia di classi, allora questo approccio può migliorare un po 'la tua applicazione rispetto alle sue prestazioni, se finisci spesso per controllare l'istanza di un puntatore. Ma usalo con cautela . Non esiste alcuna garanzia per la correttezza di questo approccio.

Nota: tutte le demo sono state compilate utilizzando clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))sotto macOS Sierra su un MacBook Pro metà 2012.

Modifica: ho anche testato le prestazioni su una macchina Linux usando gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. Su questa piattaforma il vantaggio in termini di prestazioni non era così significativo come su macOs con clang.

Output (senza ottimizzazione del compilatore):

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

Output (con ottimizzazione del compilatore):

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

Risposta ben ponderata! Sono contento che tu abbia fornito i tempi. Questa è stata una lettura interessante.
Eric,

0

dynamic_castè noto per essere inefficiente. Attraversa la gerarchia dell'ereditarietà ed è l'unica soluzione se si dispone di più livelli di ereditarietà e deve verificare se un oggetto è un'istanza di uno dei tipi nella sua gerarchia di tipi.

Ma se una forma più limitata di instanceofciò verifica solo se un oggetto è esattamente il tipo specificato, è sufficiente per le tue esigenze, la funzione di seguito sarebbe molto più efficiente:

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

Ecco un esempio di come invocheresti la funzione sopra:

DerivedA k;
Base *p = &k;

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

Dovresti specificare il tipo di modello A(come il tipo che stai cercando) e passare l'oggetto che vuoi testare come argomento (da quale tipo di modello Ksarebbe dedotto).


Lo standard non richiede che hash_code sia univoco per diversi tipi, quindi non è affidabile.
Mattnz,

2
Typeid (T) non è paragonabile all'uguaglianza, quindi non è necessario fare affidamento sull'hashcode?
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);
 }

1
Questo è davvero un cattivo esempio. Perché non usare il sovraccarico, che è più economico?
user1095108

11
Il problema principale è che non riesce a rispondere alla domanda. instanceofinterroga il tipo dinamico, ma in questa risposta il tipo dinamico e statico corrisponde sempre.
Salterio il

@HHH la tua risposta è lontana dalla domanda che ti viene posta!
programmatore

-11

Questo ha funzionato perfettamente per me usando Code :: Blocks IDE con 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;
}

1
@programmer Penso che intendi chiamare @pgp, ho semplicemente corretto la sua formattazione del codice. Inoltre, la sua risposta sembra fondamentalmente essere "use typeid", che mentre è errato ("Non vi è alcuna garanzia che la stessa istanza std :: type_info verrà citata da tutte le valutazioni dell'espressione typeid sullo stesso tipo ... assert(typeid(A) == typeid(A)); /* not guaranteed */", vedere cppreference.com ), indica che almeno ha cercato di rispondere alla domanda, anche se inutilmente perché ha trascurato di offrire un esempio minimo di lavoro.
Andres Riofrio,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.