Javaのinstanceofに相当するC ++


202

Javaと同等のC ++を実現するために推奨される方法は何instanceofですか?


57
パフォーマンスと互換性により好まれる...
ユヴァルアダム

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サポートを有効にする必要があります。

編集:私はこの答えについていくつかの良いコメントをしました!

dynamic_cast(またはinstanceof)を使用する必要があるたびに、それが必要かどうかを自問することをお勧めします。これは一般的にデザインが悪いことを示しています。

典型的な回避策は、チェックするクラスの特別な動作を基本クラスの仮想関数に入れるか、またはインターフェイスを変更せずにサブクラスの特定の動作を導入できるビジターのようなものを導入することです(ビジター受け入れインターフェイスの追加を除く)コース)。

指摘したように、dynamic_castは無料ではありません。ほとんど(すべてのケースではない)を処理する単純で一貫して実行されるハックは、基本的に、クラスが持つことができるすべての型を表す列挙型を追加し、正しい型を取得したかどうかを確認します。

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

これは良いooデザインではありませんが、回避策になる可能性があり、そのコストは多かれ少なかれ仮想関数呼び出しのみです。また、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」チェックを行う場合に
当てはまり

7
instanceofを使用する必要がある場合、ほとんどの場合、設計に問題があります。
mslot 2009

24
dynamic_castは大きなコストのかかる操作であることを忘れないでください。
Klaim、2009

13
動的型テストの合理的な使用例は数多くあります。通常は好ましくありませんが、場所があります。(それ以外の場合、なぜそれまたは同等のものがすべての主要なOO言語で表示されるのですか:C ++、Java、Pythonなど)
Paul Draper

2
両方を区別して処理する必要がない場合は、両方ともIOExceptionレベルでキャッチします。それらを別々に処理する必要がある場合は、例外ごとにキャッチブロックを追加します。
mslot 2013

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しかし、正しいポインタを取得するように注意してください。ポリモーフィックポインターでは無効ですか?
Adrian Maire、2015年

この関数を使用するときにポインターを逆参照するとどうなりますか?それから、それは多態性ポインターに対して機能しますか?
mark.kedzierski 2015

いいえ、これはコンパイラーが認識している型でのみ機能します。逆参照するかどうかに関係なく、ポリモーフィックポインターを処理しません。その場合でも機能するものを追加します。
panzi

2
ポインタの代わりに参照を使用するこのメソッドのバージョンを作成するように、サンプルを変更しました。cpp.sh
Sri Harsha Chilakapati '28

動的キャストの「const」のターゲットタイプがあるのはなぜですか?
user1056903 2016

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サポートに依存しています。だからここにいくつかのマクロとメタプログラミングマジックに依存するこの問題の私の解決策があります。唯一の欠点は、このアプローチが多重継承に対して機能しないことです。

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しかし私たちのバイナリ使用してコンパイラotpimizationを調整することにより、潮のターン。それぞれのコンパイラコマンドは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))、MacBook Pro Mid 2012のmacOS Sierraでコンパイルされました。

編集: を使用してLinuxマシンでのパフォーマンスもテストしましたgcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609。このプラットフォームでは、パフォーマンスのメリットは、clangを備えたmacOほど大きくありませんでした。

出力(コンパイラー最適化なし):

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)自体は同等と比較できないので、ハッシュコードに依存する必要はありませんか?
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
これは本当に悪い例です。オーバーロードを使用しないのはなぜですか?
user1095108 2013

11
主な問題は、質問に答えられないことです。instanceof動的タイプを照会しますが、この回答では、動的タイプと静的タイプは常に対応しています。
MSalters 2013

@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
@プログラマー@pgpを呼び出すつもりだと思います。私は彼のコード形式を修正しました。また、彼の答えは基本的に「使用typeid」であるようですが、これは間違っています(「同じstd :: type_infoインスタンスが同じタイプのtypeid式のすべての評価によって参照される保証はありません... assert(typeid(A) == typeid(A)); /* not guaranteed */」、「cppreference.com)は、最小限の実用的な例を提供しなかったために役に立たなかったとしても、少なくとも質問に答えようとしたことを示しています。
Andres Riofrio 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.