C ++ еквівалент екземпляра Java


202

Який кращий метод для досягнення C ++ еквівалента Java instanceof?


57
Кращий за продуктивністю та сумісністю ...
Yuval Adam

7
не справедливо запитувати "instanceof - якою мовою?"
mysticcoder

3
@mysticcoder: Я отримую "наприклад на" для болгарської мови, GT не підтримує C ++, хоча
Mark K Cowan

Відповіді:


200

Спробуйте скористатися:

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

Для цього потрібен ваш компілятор з підтримкою rtti.

EDIT: У мене було кілька хороших коментарів до цієї відповіді!

Кожен раз, коли вам потрібно використовувати динамічний_каст (або екземпляр), краще запитайте себе, чи це потрібна річ. Це взагалі ознака поганого дизайну.

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

Як вказувалося, динамичний канал не приходить безкоштовно. Простий і послідовно виконуючий хак, який обробляє більшість (але не у всіх випадках) - це в основному додавання перерахунків, що представляють усі можливі типи, які може мати ваш клас, і перевірити, чи правильно ви отримали.

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
Не забувайте, що динамичний канал є операцією з великими витратами.
Клайм

13
Існує багато прикладів розумного використання тестування динамічного типу. Зазвичай це не є кращим, але воно має місце. (В іншому випадку, чому він або його еквівалент з'являться у всіх основних мовах ОО: C ++, Java, Python тощо?)
Пол Дрейпер,

2
Я б спіймав обох на рівні IOException, якщо з ними не потрібно буде оброблятись важко. Якщо з ними потрібно по-різному оброблятись, я б додав блок ловлі для кожного винятку.
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"?
користувач1056903

7

Екземпляр реалізації без динамічної передачі

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

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

Але ти все ще покладається на RTTIпідтримку. Тому ось моє рішення цієї проблеми залежно від деяких макросів та метапрограмування магії. Єдиний недолік імхо полягає в тому, що такий підхід не працює для багаторазового успадкування .

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 середині 2012 року.

Редагувати: Я також перевіряв продуктивність на машині Linux, використовуючи gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. На цій платформі вигода від продуктивності була не настільки значною, як на macOs з клангом.

Вихід (без оптимізації компілятора):

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буде зроблено висновок).


Стандарт не вимагає, щоб хеш-код був унікальним для різних типів, тому це ненадійно.
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
Це дійсно поганий приклад. Чому б не використовувати перевантаження, тобто дешевше?
користувач1095108

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", яке, хоча і невірно ("Немає гарантії, що той самий екземпляр std :: type_info буде посилатися на всі оцінки типового виразу одного типу ... assert(typeid(A) == typeid(A)); /* not guaranteed */", див. cppreference.com ), вказує на те, що він хоча б намагався відповісти на це питання, якщо це не довічно, тому що він нехтував запропонувати мінімальний робочий приклад.
Андрес Ріофріо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.