C++中与Java的instanceof相当的语法是什么?

234

如何实现C++中类似Java的instanceof功能?


59
性能和兼容性更佳... - Yuval Adam
7
询问“instanceof - 在哪种语言中?”是公平的吗? - mysticcoder
5
@mysticcoder:我得到的是保加利亚语的“например на”,但GT不支持C++。 - Mark K Cowan
5个回答

219

尝试使用:

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

这需要您启用编译器的 rtti 支持。

编辑: 我在这个答案中得到了一些好的评论!

每当您需要使用 dynamic_cast(或 instanceof)时,最好问问自己是否必要。通常这是设计不良的标志。

典型的解决方法是将您正在检查的类的特殊行为放入基类的虚函数中,或者引入像访问者模式这样的东西,在无需更改接口的情况下为子类引入特定行为(当然,需要添加访问者接受接口)。

正如指出的,dynamic_cast 不是免费的。一个简单而始终如一地执行大多数(但不是所有)情况的 hack 是基本上添加枚举来表示您的类可能具有的所有可能类型,并检查是否选择了正确的类型。

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
}

7
当你进行"instanceof"检查时,通常情况是这样的。 - Laserallan
8
如果你不得不使用 instanceof,那么在大多数情况下,你的设计存在问题。 - mslot
26
不要忘记,dynamic_cast 是一项代价很大的操作。 - Klaim
18
有许多合理使用动态类型测试的示例,尽管它并不常用,但确实有其应用场景。(否则,为什么它或其等效物会出现在每个主要的面向对象语言中:C++、Java、Python等?) - Paul Draper
2
如果它们不需要被不同地处理,我会在IOException级别捕获两者。如果它们需要被不同地处理,那么我会为每个异常添加一个catch块。 - mslot
显示剩余8条评论

47

根据您想要做什么,您可以这样做:

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
如果我们在使用这个函数时解除指针引用,那么它是否可以用于多态指针? - mark.kedzierski
这仅适用于编译器已知的类型。无论您是否取消引用,都无法使用多态指针。不过,我会添加一些在这种情况下可能有效的内容。 - panzi
3
我已经修改了您的示例,编写了一个使用引用而不是指针的方法版本:http://cpp.sh/8owv - Sri Harsha Chilakapati
为什么动态转换的目标类型是“const”? - user1056903
显示剩余3条评论

15

不使用dynamic_cast实现Instanceof

我认为这个问题今天仍然很重要。使用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和模板元编程,并且不太懒得为类层次结构的类添加一些小指令,那么这种方法可以在性能方面提高应用程序的效率,特别是当你经常需要检查指针的实例时。但要谨慎使用。该方法的正确性没有保证。
注:所有演示都是在MacBook Pro 2012年中使用macOS Sierra下的clang(Apple LLVM版本9.0.0(clang-900.0.39.2))编译的。
编辑:我还在Linux机器上使用gcc(Ubuntu 5.4.0-6ubuntu1〜16.04.9)5.4.0 20160609测试了性能。在这个平台上,性能收益不像在macOS上使用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

1
思路清晰的回答!很高兴你提供了时间安排。这是一篇有趣的阅读。 - Eric

2

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
3
typeid(T)本身是否可比较相等,因此不需要依赖于哈希码吗? - Paul Stelian
我发现dynamic_cast是胡扯。如果你有int i; A a = (A)&i; dynamic_cast不会返回NULL。而且如果你有T t; A a = (A)&t; 其中T是从A派生出来的,dynamic_cast也不会返回NULL。当然,一个基类不能是一个派生类的实例。 - user13947194

-4
#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,你的回答完全偏离了问题的要点! - programmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接