C ++ equivalente à instância de java

202

Qual é o método preferido para obter o equivalente em C ++ dos java's instanceof?

Yuval Adam
fonte
57
Preferido pelo desempenho e compatibilidade ...
Yuval Adam 01/02/09
7
não é justo perguntar "exemplo - em que idioma?"
Mysticcoder
3
@mysticcoder: eu recebo "например на" para Búlgaro, que GT não suporta C ++ embora
Mark K Cowan

Respostas:

200

Tente usar:

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

Isso requer que seu compilador tenha o suporte a rtti ativado.

EDIT: Eu tive alguns bons comentários sobre esta resposta!

Toda vez que você precisar usar um dynamic_cast (ou instanceof), é melhor você se perguntar se é necessário. Geralmente é um sinal de mau design.

As soluções alternativas típicas são colocar o comportamento especial da classe que você está verificando em uma função virtual na classe base ou talvez introduzir algo como um visitante em que você possa introduzir um comportamento específico para subclasses sem alterar a interface (exceto adicionar a interface de aceitação do visitante de curso).

Como apontado, o dynamic_cast não é de graça. Um hack simples e de desempenho consistente que lida com a maioria (mas não todos os casos) é basicamente adicionar uma enumeração que representa todos os tipos possíveis que sua classe pode ter e verificar se você acertou.

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

Isso não é bom para o design, mas pode ser uma solução alternativa e seu custo é mais ou menos apenas uma chamada de função virtual. Também funciona, independentemente de o RTTI estar ativado ou não.

Observe que essa abordagem não oferece suporte a vários níveis de herança; portanto, se você não tomar cuidado, poderá terminar com um código parecido com o seguinte:

// 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
fonte
4
Isso é geralmente o caso quando você faz uma "instanceof" cheque
Laserallan
7
Se você precisar usar instanceof, na maioria dos casos, há algo errado com seu design.
Mslot
24
Não esqueça que dynamic_cast é uma operação com alto custo.
Klaim
13
Existem muitos exemplos de usos razoáveis ​​do teste de tipo dinâmico. Geralmente não é o preferido, mas tem um lugar. (Caso contrário, por que ele ou seu equivalente aparecem em todas as grandes OO-language: C ++, Java, Python, etc.?)
Paul Draper
2
Eu pegaria os dois no nível IOException se eles não precisassem ser tratados de maneira diferente. Se eles precisarem ser tratados de maneira diferente, eu adicionaria um bloco de captura para cada exceção.
mslot
37

Dependendo do que você deseja fazer, você pode fazer o seguinte:

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

Usar:

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

No entanto, isso opera puramente nos tipos conhecidos pelo compilador.

Editar:

Este código deve funcionar para ponteiros polimórficos:

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

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

panzi
fonte
Solução elegante e bem feita. +1 Mas tenha cuidado para obter o ponteiro correto. Não é válido para ponteiro polimórfico?
Adrian Maire
E se adiarmos o ponteiro ao usar esta função? Funcionaria então para ponteiros polimórficos?
mark.kedzierski
Não, isso opera apenas nos tipos conhecidos pelo compilador. Não funcionará com ponteiros polimórficos, não importa se você prefere ou não. Vou adicionar algo que pode funcionar nesse caso.
panzi 5/09
2
Eu modifiquei o seu exemplo para escrever uma versão deste método que usa referências em vez de ponteiros: cpp.sh/8owv
Sri Harsha Chilakapati
Por que o tipo de destino do elenco dinâmico "const"?
User1056903
7

Instância de implementação sem dynamic_cast

Eu acho que essa questão ainda é relevante hoje. Usando o padrão C ++ 11, agora você pode implementar uma instanceoffunção sem usar o dynamic_castseguinte:

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

Mas você ainda depende do RTTIsuporte. Então, aqui está minha solução para esse problema, dependendo de algumas macros e da metaprogramação mágica. A única desvantagem é que essa abordagem não funciona para herança múltipla .

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

Você pode usar esse material ( com cuidado ) da seguinte maneira:

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)

O código a seguir apresenta uma pequena demonstração para verificar o comportamento correto rudimentar.

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

Resultado:

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

atuação

A questão mais interessante que surge agora é se esse material maligno é mais eficiente do que o uso dynamic_cast. Portanto, eu escrevi um aplicativo de medição de desempenho muito básico.

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

Os resultados variam e baseiam-se essencialmente no grau de otimização do compilador. Compilar o programa de medição de desempenho usando g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppa saída na minha máquina local foi:

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, esse resultado foi muito preocupante, porque os horários demonstram que a nova abordagem não é muito mais rápida em comparação com a dynamic_castabordagem. É ainda menos eficiente para o caso de teste especial que testa se um ponteiro de Aé uma instância de A. MAS a maré vira ajustando nosso binário usando otimização do compilador. O respectivo comando do compilador é g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. O resultado na minha máquina local foi incrível:

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 você não depende de herança múltipla, não se opõe a boas macros C antigas, RTTI e metaprogramação de modelos e não tem preguiça de adicionar algumas instruções pequenas às classes de sua hierarquia de classes, essa abordagem pode aumentar um pouco seu aplicativo com relação ao seu desempenho, se você costuma verificar a instância de um ponteiro. Mas use-o com cautela . Não há garantia para a correção dessa abordagem.

Nota: Todas as demos foram compiladas usando o clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))macOS Sierra em um MacBook Pro meados de 2012.

Edit: Eu também testei o desempenho em uma máquina Linux usando gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. Nesta plataforma, o benefício de desempenho não era tão significativo quanto nos macOs com clang.

Saída (sem otimização do compilador):

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

Saída (com otimização do compilador):

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
fonte
Resposta bem pensada! Estou feliz que você forneceu os horários. Esta foi uma leitura interessante.
Eric
0

dynamic_casté conhecido por ser ineficiente. Ele percorre a hierarquia de herança e é a única solução se você tiver vários níveis de herança e precisa verificar se um objeto é uma instância de qualquer um dos tipos em sua hierarquia de tipos.

Mas se uma forma mais limitada instanceofdisso apenas verificar se um objeto é exatamente do tipo especificado, é suficiente para suas necessidades, a função abaixo seria muito mais eficiente:

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

Aqui está um exemplo de como você chamaria a função acima:

DerivedA k;
Base *p = &k;

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

Você especificaria o tipo de modelo A(como o tipo que está verificando) e passaria o objeto que deseja testar como argumento (a partir do qual o tipo de modelo Kseria inferido).

Arjun Menon
fonte
O padrão não exige que o hash_code seja exclusivo para tipos diferentes, portanto, isso não é confiável.
mattnz
2
O typeid (T) não é comparável à igualdade; portanto, não é necessário confiar no código de hash?
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
fonte
1
Este é um exemplo muito ruim. Por que não usar sobrecarga, isso é mais barato?
User1095108
11
O principal problema é que ele não responde à pergunta. instanceofconsulta o tipo dinâmico, mas nesta resposta o tipo dinâmico e estático sempre corresponde.
MSalters
@HHH você responde está muito longe da pergunta que está sendo feita!
Programador
-11

Isso funcionou perfeitamente para mim usando o Code :: Blocks IDE com complemento do 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
fonte
1
@ programador Eu acho que você quer chamar @pgp, eu simplesmente consertei a formatação do código dele. Além disso, sua resposta parece ser basicamente "use typeid", que enquanto incorreta ("Não há garantia de que a mesma instância std :: type_info seja referida por todas as avaliações da expressão typeid no mesmo tipo ... assert(typeid(A) == typeid(A)); /* not guaranteed */", consulte cppreference.com ), indica que ele tentou pelo menos responder à pergunta, se não ajudou , porque deixou de oferecer um exemplo de trabalho mínimo.
Andres Riofrio