C ++ setara dengan instanceof java


202

Apa metode yang disukai untuk mencapai setara C ++ dari java instanceof?


57
Lebih disukai oleh kinerja dan kompatibilitas ...
Yuval Adam

7
tidak adil bertanya "contoh - dalam bahasa apa?"
mysticcoder

3
@mysticcoder: Saya mendapatkan "например на" untuk bahasa Bulgaria, GT tidak mendukung C ++
Mark K Cowan

Jawaban:


200

Coba gunakan:

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

Ini membutuhkan kompiler Anda untuk mengaktifkan dukungan rtti.

EDIT: Saya punya beberapa komentar bagus tentang jawaban ini!

Setiap kali Anda perlu menggunakan dynamic_cast (atau instanceof) Anda sebaiknya bertanya pada diri sendiri apakah itu hal yang perlu. Ini umumnya merupakan tanda dari desain yang buruk.

Pemecahan umum adalah menempatkan perilaku khusus untuk kelas yang Anda periksa menjadi fungsi virtual di kelas dasar atau mungkin memperkenalkan sesuatu seperti pengunjung mana Anda dapat memperkenalkan perilaku spesifik untuk subkelas tanpa mengubah antarmuka (kecuali untuk menambahkan antarmuka penerimaan pengunjung dari tentu saja).

Seperti yang ditunjukkan dynamic_cast tidak datang secara gratis. Peretasan sederhana dan secara konsisten melakukan yang menangani sebagian besar (tetapi tidak semua kasus) pada dasarnya menambahkan enum yang mewakili semua jenis yang mungkin dimiliki kelas Anda dan memeriksa apakah Anda mendapatkannya dengan benar.

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

Ini bukan desain yang bagus, tetapi bisa menjadi solusi dan biayanya lebih atau kurang hanya panggilan fungsi virtual. Ini juga berfungsi terlepas dari RTTI diaktifkan atau tidak.

Perhatikan bahwa pendekatan ini tidak mendukung banyak level pewarisan jadi jika Anda tidak berhati-hati, Anda mungkin berakhir dengan kode yang terlihat seperti ini:

// 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
Itu umumnya terjadi ketika Anda melakukan pemeriksaan "instanceof"
Laserallan

7
Jika Anda harus menggunakan instanceof, ada, dalam banyak kasus, ada yang salah dengan desain Anda.
mslot

24
Jangan lupa bahwa dynamic_cast adalah operasi dengan biaya besar.
Klaim

13
Ada banyak contoh penggunaan wajar pengujian tipe dinamis. Biasanya tidak disukai, tetapi memiliki tempat. (Kalau tidak, mengapa itu atau yang setara akan muncul di setiap bahasa OO utama: C ++, Java, Python, dll?)
Paul Draper

2
Saya akan menangkap keduanya pada tingkat IOException jika mereka tidak perlu ditangani secara berbeda. Jika mereka perlu ditangani secara berbeda, maka saya akan menambahkan blok penangkap untuk setiap pengecualian.
mslot

37

Bergantung pada apa yang ingin Anda lakukan, Anda bisa melakukan ini:

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

Menggunakan:

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

Namun, ini murni beroperasi pada tipe yang dikenal oleh kompiler.

Edit:

Kode ini harus berfungsi untuk petunjuk polimorfik:

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

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


Solusi elegan dan dilakukan dengan baik. +1 Tapi hati-hati untuk mendapatkan pointer yang benar. Tidak berlaku untuk penunjuk polimorfik?
Adrian Maire

Bagaimana jika kita menunda penunjuk saat menggunakan fungsi ini? Apakah ini akan berfungsi untuk petunjuk polimorfik?
mark.kedzierski

Tidak, ini hanya beroperasi pada tipe yang diketahui oleh kompiler. Tidak akan bekerja dengan petunjuk polimorfik, tidak masalah apakah Anda berdereerea atau tidak. Saya akan menambahkan sesuatu yang mungkin berfungsi dalam kasus itu.
panzi

2
Saya telah memodifikasi contoh Anda untuk menulis versi metode ini yang menggunakan referensi alih-alih petunjuk: cpp.sh/8owv
Sri Harsha Chilakapati

Mengapa memiliki tipe target cast "const" dinamis?
user1056903

7

Instanceof implementasi tanpa dynamic_cast

Saya pikir pertanyaan ini masih relevan sampai sekarang. Menggunakan standar C ++ 11 Anda sekarang dapat mengimplementasikan instanceoffungsi tanpa menggunakan dynamic_castseperti ini:

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

Tetapi Anda masih bergantung pada RTTIdukungan. Jadi di sini adalah solusi saya untuk masalah ini tergantung pada beberapa Macro dan Metaprogramming Magic. Satu-satunya kelemahan imho adalah bahwa pendekatan ini tidak berfungsi untuk pewarisan berganda .

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

Anda kemudian dapat menggunakan hal-hal ini ( dengan hati-hati ) sebagai berikut:

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)

Kode berikut menyajikan demo kecil untuk memverifikasi dasar perilaku yang benar.

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

Keluaran:

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

Performa

Pertanyaan paling menarik yang sekarang muncul adalah, apakah hal jahat ini lebih efisien daripada penggunaan dynamic_cast. Karena itu saya telah menulis aplikasi pengukuran kinerja yang sangat mendasar.

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

Hasilnya bervariasi dan pada dasarnya didasarkan pada tingkat optimisasi kompiler. Mengkompilasi program pengukuran kinerja menggunakan g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppoutput pada mesin lokal saya adalah:

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, hasil ini sangat serius, karena timing menunjukkan bahwa pendekatan baru tidak jauh lebih cepat dibandingkan dengan dynamic_castpendekatan. Ini bahkan kurang efisien untuk kasus uji khusus yang menguji apakah pointer Aadalah turunan dari A. TAPI ombak berubah dengan menyetel biner kami menggunakan otpimisasi kompiler. Perintah kompiler masing-masing adalah g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. Hasilnya di mesin lokal saya sangat mengagumkan:

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

Jika Anda tidak bergantung pada multiple inheritance, tidak ada lawan dari macro C tua yang baik, RTTI dan metaprogramming template dan tidak terlalu malas untuk menambahkan beberapa instruksi kecil ke kelas-kelas hierarki kelas Anda, maka pendekatan ini dapat meningkatkan aplikasi Anda sedikit sehubungan dengan kinerjanya, jika Anda sering berakhir dengan memeriksa contoh pointer. Tapi gunakan dengan hati-hati . Tidak ada jaminan untuk kebenaran pendekatan ini.

Catatan: Semua demo dikompilasi menggunakan di clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))bawah macOS Sierra pada MacBook Pro Mid 2012.

Sunting: Saya juga telah menguji kinerja menggunakan mesin Linux gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. Pada platform ini, manfaat kinerja tidak begitu signifikan seperti pada macOs dengan dentang.

Output (tanpa optimasi kompiler):

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 (dengan optimisasi kompiler):

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

Jawaban yang dipikirkan dengan matang! Saya senang Anda memberikan timing. Ini bacaan yang menarik.
Eric

0

dynamic_castdiketahui tidak efisien. Itu melintasi hierarki warisan, dan itu adalah satu-satunya solusi jika Anda memiliki beberapa tingkat warisan, dan perlu memeriksa apakah suatu objek adalah instance dari salah satu dari jenis dalam hierarki tipenya.

Tetapi jika bentuk yang lebih terbatas dari instanceofitu hanya memeriksa apakah suatu objek persis dengan jenis yang Anda tentukan, cukup untuk kebutuhan Anda, fungsi di bawah ini akan jauh lebih efisien:

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

Berikut ini contoh cara Anda menjalankan fungsi di atas:

DerivedA k;
Base *p = &k;

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

Anda akan menentukan jenis templat A(sebagai jenis yang Anda periksa), dan meneruskan objek yang ingin Anda uji sebagai argumen (dari jenis templat mana yang Kakan disimpulkan).


Standar tidak mengharuskan kode hash menjadi unik untuk jenis yang berbeda, jadi ini tidak bisa diandalkan.
mattnz

2
Bukankah typeid (T) itu sendiri sebanding dengan persamaan, jadi tidak perlu bergantung pada kode 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);
 }

1
Ini adalah contoh yang sangat buruk. Mengapa tidak menggunakan overloading, itu lebih murah?
user1095108

11
Masalah utamanya adalah gagal menjawab pertanyaan. instanceofmenanyakan tipe dinamis, tetapi dalam jawaban ini tipe dinamis dan statis selalu sesuai.
MSalters

@HHH jawaban Anda jauh dari pertanyaan yang diajukan!
programmer

-11

Ini bekerja sempurna untuk saya menggunakan Code :: Blocks IDE dengan 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 Saya pikir Anda bermaksud memanggil @pgp, saya hanya memperbaiki pemformatan kodenya. Selain itu, jawabannya pada dasarnya adalah "menggunakan typeid", yang sementara salah ("Tidak ada jaminan bahwa tur std :: type_info yang sama akan dirujuk oleh semua evaluasi ekspresi typeid pada tipe yang sama ... assert(typeid(A) == typeid(A)); /* not guaranteed */", lihat cppreference.com ), memang menunjukkan bahwa ia setidaknya mencoba menjawab pertanyaan, jika tidak membantu karena ia lalai menawarkan contoh kerja minimal.
Andres Riofrio
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.