C ++ эквивалент java instanceof


202

Каков предпочтительный метод для достижения C ++ эквивалента Java instanceof?


57
Предпочтение отдается производительности и совместимости ...
Ювал Адам

7
разве не справедливо спросить «instanceof - на каком языке?»
Мистикодер

3
@mysticcoder: я получаю «например на» для болгарского языка, хотя GT не поддерживает C ++
Марк К Коуэн

Ответы:


200

Попробуйте использовать:

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

Это требует, чтобы ваш компилятор включил поддержку rtti.

РЕДАКТИРОВАТЬ: у меня были некоторые хорошие комментарии к этому ответу!

Каждый раз, когда вам нужно использовать dynamic_cast (или instanceof), вам лучше спросить себя, является ли это необходимым. Это вообще признак плохого дизайна.

Типичные обходные пути - это размещение специального поведения для класса, который вы проверяете, в виртуальной функции базового класса или, возможно, введение чего-то вроде посетителя, где вы можете ввести определенное поведение для подклассов без изменения интерфейса (за исключением добавления интерфейса принятия посетителем курс).

Как указано, dynamic_cast не предоставляется бесплатно. Простой и последовательно выполняемый хак, который обрабатывает большинство (но не все случаи), в основном добавляет enum, представляющий все возможные типы, которые может иметь ваш класс, и проверяет, правильно ли вы выбрали.

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

Это не очень хороший дизайн, но это может быть обходной путь, и его стоимость более или менее только вызов виртуальной функции. Он также работает независимо от того, включен RTTI или нет.

Обратите внимание, что этот подход не поддерживает несколько уровней наследования, поэтому, если вы не будете осторожны, вы можете получить код, похожий на этот:

// 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
Это обычно тот случай, когда вы делаете проверку «instanceof»
Laserallan

7
Если вам нужно использовать instanceof, в большинстве случаев что-то не так с вашим дизайном.
mslot

24
Не забывайте, что dynamic_cast - это операция с большими затратами.
Klaim

13
Есть много примеров разумного использования динамического тестирования типов. Это обычно не предпочитается, но у него есть место. (В противном случае, почему он или его эквивалент появляются в каждом основном ОО-языке: C ++, Java, Python и т. Д.?)
Пол Дрейпер

2
Я бы поймал оба на уровне IOException, если их не нужно обрабатывать по-разному. Если их нужно обрабатывать по-разному, я бы добавил блок catch для каждого исключения.
mslot

37

В зависимости от того, что вы хотите сделать, вы можете сделать это:

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

Использование:

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

Однако это чисто работает с типами, известными компилятору.

Редактировать:

Этот код должен работать для полиморфных указателей:

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

Пример: http://cpp.sh/6qir


Элегантное и хорошо выполненное решение. +1 Но будьте осторожны, чтобы получить правильный указатель. Не подходит для полиморфного указателя?
Адриан Мэйр

Что если мы используем указатель при использовании этой функции? Будет ли это работать для полиморфных указателей?
mark.kedzierski

Нет, это действует только на типы, известные компилятору. Не будет работать с полиморфными указателями, независимо от того, используете ли вы разыменование или нет. Я добавлю кое-что, что могло бы работать в этом случае все же.
Panzi

2
Я изменил ваш пример, чтобы написать версию этого метода, которая использует ссылки вместо указателей: cpp.sh/8owv
Шри Харша Чилакапати

Почему целевой тип динамического приведения "const"?
user1056903

7

Экземпляр реализации без dynamic_cast

Я думаю, что этот вопрос все еще актуален сегодня. Используя стандарт C ++ 11, вы теперь можете реализовать instanceofфункцию без использования dynamic_castэтого:

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

Но вы все еще полагаетесь на RTTIподдержку. Итак, вот мое решение этой проблемы в зависимости от некоторых макросов и метапрограммирования магии. Единственный недостаток imho заключается в том, что этот подход не работает для множественного наследования .

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

демонстрация

Затем вы можете использовать этот материал ( с осторожностью ) следующим образом:

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)

В следующем коде представлена ​​небольшая демонстрация для проверки правильного поведения.

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

Вывод:

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

Производительность

Самый интересный вопрос, который сейчас возникает, заключается в том, является ли этот злой материал более эффективным, чем использование dynamic_cast. Поэтому я написал очень простое приложение для измерения производительности.

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

Результаты различаются и в основном зависят от степени оптимизации компилятора. Компиляция программы измерения производительности с использованием g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppвывода на моем локальном компьютере:

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

Мм, этот результат был очень отрезвляющим, потому что время показывает, что новый подход не намного быстрее по сравнению с dynamic_castподходом. Это еще менее эффективно для специального теста, который проверяет, является ли указатель Aэкземпляра A. НО прилив меняется, настраивая наш бинарный файл с помощью отпимеризации компилятора. Соответствующая команда компилятора g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. Результат на моей локальной машине был потрясающим:

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

Если вы не полагаетесь на множественное наследование, не являетесь противником старых добрых макросов C, RTTI и шаблонного метапрограммирования и не поленитесь добавить небольшие инструкции к классам иерархии классов, тогда этот подход может немного улучшить ваше приложение. в отношении его производительности, если вам часто приходится проверять экземпляр указателя. Но используйте это с осторожностью . На правильность такого подхода нет никаких гарантий.

Примечание. Все демонстрационные версии были скомпилированы с использованием clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))MacOS Sierra на MacBook Pro Mid 2012.

Изменить: я также проверил производительность на машине Linux с помощью gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. На этой платформе выигрыш в производительности был не таким значительным, как на macO с clang.

Вывод (без оптимизации компилятора):

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

Вывод (с оптимизацией компилятора):

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

Хорошо продуманный ответ! Я рад, что вы предоставили время. Это было интересное чтение.
Эрик

0

dynamic_castкак известно, неэффективно. Он пересекает иерархию наследования и является единственным решением, если у вас есть несколько уровней наследования, и вам необходимо проверить, является ли объект экземпляром какого-либо одного из типов в его иерархии типов.

Но если более ограниченная форма instanceofпроверки только проверяет, является ли объект именно того типа, который вы указали, достаточно для ваших нужд, приведенная ниже функция будет намного более эффективной:

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

Вот пример того, как вы бы вызвали функцию выше:

DerivedA k;
Base *p = &k;

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

Вы должны указать тип шаблона A(как тип, который вы проверяете) и передать в качестве аргумента объект, который вы хотите проверить (из какого типа шаблона Kбудет выведен вывод).


Стандарт не требует, чтобы hash_code был уникальным для разных типов, поэтому это ненадежно.
Mattnz

2
Разве typeid (T) сам по себе не сопоставим с равенством, поэтому не требуется опора на хеш-код?
Пол Стелиан

-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
Это действительно плохой пример. Почему бы не использовать перегрузку, которая дешевле?
user1095108

11
Основная проблема заключается в том, что он не может ответить на вопрос. instanceofзапрашивает динамический тип, но в этом ответе динамический и статический тип всегда соответствуют.
MSalters

@HHH, ты отвечаешь, это далеко от вопроса!
программист

-11

Это прекрасно работает для меня, используя Code :: Blocks IDE с компилятором 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;
}

1
@programmer Я думаю, вы хотите вызвать @pgp, я просто исправил его форматирование кода. Кроме того, его ответ, по-видимому, в основном «используется typeid», что, хотя и неверно («Нет гарантии, что все оценки выражения typeid для одного и того же типа будут ссылаться на один и тот же экземпляр std :: type_info assert(typeid(A) == typeid(A)); /* not guaranteed */», см. cppreference.com ), указывает на то, что он хотя бы попытался ответить на вопрос, хотя и безуспешно, потому что он пренебрег минимальным рабочим примером.
Андрес Риофрио
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.