【问题标题】:How do I check if an object's type is a particular subclass in C++?如何检查对象的类型是否是 C++ 中的特定子类?
【发布时间】:2010-09-23 09:26:49
【问题描述】:

我正在考虑使用typeid(),但我不知道如何询问该类型是否是另一个类的子类(顺便说一句,它是抽象的)

【问题讨论】:

  • 我只是想知道是否有办法在 C++ 中检查对象的类型是否是在编译时的特定子类,因为std::is_base_of 无法按预期工作. :3
  • 您能否详细说明“std::is_base_of 无法正常工作”?我用 gcc C++20 和 VS C++17 都试过了;当类不是从基类派生时,它给了我一个编译器错误。例如,对于struct A {};struct B {}; 这两个结构,static_assert (std::is_base_of_v <A, B>); 行在编译时失败,但对于struct DerivedFromA : public A {};static_assert (std::is_base_of_v<A, DerivedFromA>); 行编译正常。

标签: c++ class subclass identification


【解决方案1】:

 

class Base
{
  public: virtual ~Base() {}
};

class D1: public Base {};

class D2: public Base {};

int main(int argc,char* argv[]);
{
  D1   d1;
  D2   d2;

  Base*  x = (argc > 2)?&d1:&d2;

  if (dynamic_cast<D2*>(x) == nullptr)
  {
    std::cout << "NOT A D2" << std::endl;
  }
  if (dynamic_cast<D1*>(x) == nullptr)
  {
    std::cout << "NOT A D1" << std::endl;
  }
}

【讨论】:

  • 你真的需要dynamic_cast&lt;&gt;吗? static_cast&lt;&gt; 还不够吗?
  • @krlmlr。你能在编译时说出x 的类型吗?如果是这样,那么static_cast&lt;&gt;() 就可以了。如果直到运行时你才知道x 的类型,那么你需要dynamic_cast&lt;&gt;()
  • 谢谢。我主要在 CRTP 中使用向下转换,我一直忘记其他用例;-)
  • 很好的答案,但这里有一点需要注意。三元条件运算符要求其第二个和第三个操作数具有相同的类型。因此,我想知道这如何以这种方式对任何人起作用,请改用 if/else。也许这在过去有效?无论如何。
  • @Nikos,它之所以有效是因为:1. C++ 不要求三元的情况是相同的类型,2. 它们是派生类指针的类型,派生类指针隐式转换为基类。跨度>
【解决方案2】:

你真的不应该。如果您的程序需要知道对象是什么类,那通常表明存在设计缺陷。看看你是否可以使用虚函数获得你想要的行为。此外,有关您正在尝试做的事情的更多信息会有所帮助。

我假设你有这样的情况:

class Base;
class A : public Base {...};
class B : public Base {...};

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

如果这是你所拥有的,那么尝试做这样的事情:

class Base
{
  virtual void bar() = 0;
};

class A : public Base
{
  void bar() {/* do X */}
};

class B : public Base
{
  void bar() {/* do Y */}
};

void foo(Base *p)
{
  p->bar();
}

编辑:由于关于这个答案的争论在这么多年后仍在继续,我想我应该提供一些参考资料。如果你有一个基类的指针或引用,并且你的代码需要知道对象的派生类,那么它就违反了Liskov substitution principleUncle Bob 将此称为“anathema to Object Oriented Design”。

【讨论】:

  • +1。我认为正确的名称是“告诉,不要问”。基本上,总是倾向于多态性(告诉对象要做什么,让实现来处理它)而不是你要求找出你正在处理的对象类型的 case/if 语句。
  • 是的 - 这一切都很好 - 但那家伙想知道如何解析类型
  • @Dima,如果有人想知道语法只是为了学习目的(假设他们正在阅读一本用 Java 编写的关于设计缺陷的书,他们需要将其翻译成 C++) ?
  • @Dima 曾经使用过定义超类的外部库吗?请尝试在那里应用您的答案。
  • 这个答案做出了相当 huge 的假设,即您可以控制需要转换为的类型并可以重写它们...例如,我正在向基于另一个 GUI 库的 GUI 库,我需要知道小部件的父级是否可滚动。原始库无法对此进行测试,因此我必须尝试将我的小部件父级转换为可滚动小部件的基类,这真的很糟糕。无论如何,关键是您遗漏了手头问题的实际答案。
【解决方案3】:

您可以使用dynamic_cast 做到这一点(至少对于多态类型)。

实际上,再想一想——您无法通过 dynamic_cast 判断它是否是特定类型——但您可以判断它是该类型还是它的任何子类。

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}

【讨论】:

  • 子类何时不是多态类型?
  • @OllieFord:当没有任何虚函数时。
  • 换一种说法,当std::is_polymorphic_v&lt;T&gt;false
【解决方案4】:

下面的代码演示了 3 种不同的方法:

  • 虚函数
  • 类型标识
  • dynamic_cast
#include <iostream>
#include <typeinfo>
#include <typeindex>

enum class Type {Base, A, B};

class Base {
public:
    virtual ~Base() = default;
    virtual Type type() const {
        return Type::Base;
    }
};

class A : public Base {
    Type type() const override {
        return Type::A;
    }
};

class B : public Base {
    Type type() const override {
        return Type::B;
    }
};

int main()
{
    const char *typemsg;
    A a;
    B b;
    Base *base = &a;             // = &b;    !!!!!!!!!!!!!!!!!
    Base &bbb = *base;

    // below you can replace    base    with  &bbb    and get the same results

    // USING virtual function
    // ======================
    // classes need to be in your control
    switch(base->type()) {
    case Type::A:
        typemsg = "type A";
        break;
    case Type::B:
        typemsg = "type B";
        break;
    default:
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING typeid
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    std::type_index ti(typeid(*base));
    if (ti == std::type_index(typeid(A))) {
        typemsg = "type A";
    } else if (ti == std::type_index(typeid(B))) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING dynamic_cast
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    if (dynamic_cast</*const*/ A*>(base)) {
        typemsg = "type A";
    } else if (dynamic_cast</*const*/ B*>(base)) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;
}

上面的程序打印了这个:

type A
type A
type A

【讨论】:

  • 这是一个很好的答案,关于性能的任何想法?似乎虚拟在这里最快而动态最慢?
【解决方案5】:

dynamic_cast 可以确定该类型是否在继承层次结构中的任何位置包含目标类型(是的,这是一个鲜为人知的功能,如果B 继承自AC,它可以将@987654325 @直接变成C*)。 typeid() 可以确定对象的确切类型。但是,这些都应该非常谨慎地使用。正如已经提到的,您应该始终避免动态类型识别,因为它表明存在设计缺陷。 (另外,如果您确定对象是目标类型,您可以使用static_cast 进行向下转换。Boost 提供了polymorphic_downcast,它将在调试模式下使用dynamic_castassert 进行向下转换,在发布模式下,它只会使用static_cast)。

【讨论】:

    【解决方案6】:

    我不知道我是否正确理解了你的问题,所以让我用我自己的话重述一遍......

    问题:给定类BD,确定D 是否是B 的子类(反之亦然?)

    解决方案:使用一些模板魔法!好的,您需要认真看看 LOKI,它是由传说中的 C++ 作家 Andrei Alexandrescu 制作的出色的模板元编程库。

    更具体地说,下载LOKI 并在源代码中包含标题TypeManip.h,然后使用SuperSubclass 类模板,如下所示:

    if(SuperSubClass<B,D>::value)
    {
    ...
    }
    

    根据文档,如果BD 的公共基础,或者BD 是同一类型的别名,SuperSubClass&lt;B,D&gt;::value 将为真。

    DB 的子类,或者DB 相同。

    我希望这会有所帮助。

    编辑:

    请注意,SuperSubClass&lt;B,D&gt;::value 的评估发生在编译时,这与某些使用 dynamic_cast 的方法不同,因此在运行时使用此系统不会受到任何惩罚。

    【讨论】:

      【解决方案7】:

      我不同意你永远不应该在 C++ 中检查对象的类型。如果你能避免它,我同意你应该这样做。说你在任何情况下都不应该这样做是太过分了。你可以用很多语言来做这件事,它可以让你的生活更轻松。例如,Howard Pinsley 在他关于 C# 的帖子中向我们展示了如何做到这一点。

      我使用 Qt 框架做了很多工作。一般来说,我按照他们做事的方式(至少在他们的框架中工作时)对我的工作进行建模。 QObject 类是所有 Qt 对象的基类。该类具有函数 isWidgetType() 和 isWindowType() 作为快速子类检查。那么为什么不能检查你自己的派生类,这在本质上是可比的?这是从其他一些帖子中衍生出来的 QObject:

      class MyQObject : public QObject
      {
      public:
          MyQObject( QObject *parent = 0 ) : QObject( parent ){}
          ~MyQObject(){}
      
          static bool isThisType( const QObject *qObj )
          { return ( dynamic_cast<const MyQObject*>(qObj) != NULL ); }
      };
      

      然后当你传递一个指向 QObject 的指针时,你可以通过调用静态成员函数来检查它是否指向你的派生类:

      if( MyQObject::isThisType( qObjPtr ) ) qDebug() << "This is a MyQObject!";
      

      【讨论】:

        【解决方案8】:
        #include <stdio.h>
        #include <iostream.h>
        
        class Base
        {
          public: virtual ~Base() {}
        
          template<typename T>
          bool isA() {
            return (dynamic_cast<T*>(this) != NULL);
          }
        };
        
        class D1: public Base {};
        class D2: public Base {};
        class D22: public D2 {};
        
        int main(int argc,char* argv[]);
        {
          D1*   d1  = new D1();
          D2*   d2  = new D2();
          D22*  d22 = new D22();
        
          Base*  x = d22;
        
          if( x->isA<D22>() )
          {
            std::cout << "IS A D22" << std::endl;
          }
          if( x->isA<D2>() )
          {
            std::cout << "IS A D2" << std::endl;
          }
          if( x->isA<D1>() )
          {
            std::cout << "IS A D1" << std::endl;
          }
          if(x->isA<Base>() )
          {
            std::cout << "IS A Base" << std::endl;
          }
        }
        

        结果:

        IS A D22
        IS A D2
        IS A Base
        

        【讨论】:

          【解决方案9】:

          我正在考虑使用typeid()...

          嗯,是的,可以通过比较来完成:typeid().name()。如果我们采用已经描述的情况,其中:

          class Base;
          class A : public Base {...};
          class B : public Base {...};
          
          void foo(Base *p)
          {
            if(/* p is A */) /* do X */
            else /* do Y */
          }
          

          foo(Base *p) 的可能实现是:

          #include <typeinfo>
          
          void foo(Base *p)
          {
              if(typeid(*p) == typeid(A))
              {
                  // the pointer is pointing to the derived class A
              }  
              else if (typeid(*p).name() == typeid(B).name()) 
              {
                  // the pointer is pointing to the derived class B
              }
          }
          

          【讨论】:

          • 为什么要混用 typeid().name() 和 typeid() 的comarison?为什么不总是比较 typeid()?
          • 我相信这表明他们都会实现相同的目标
          【解决方案10】:

          我在这里看到了一些很好的答案,但我看到了一些愚蠢的回应。

          “试图查询对象的类型是一个设计缺陷”。这意味着Java instanceof 和C# 是关键字是设计缺陷。这些是不评价多态性的人的反应。如果您有一个接口,则该接口是由另一个实现更多功能的接口派生的。如果您需要这些额外的功能,您必须首先检查您是否有这样的接口。甚至 microsoft COM API 也使用了这种设计。

          那么在如何推断一个对象是否是一个类的实例方面,已经给出了很多很好的答案

          • 类型标识
          • 具有虚类型函数
          • 动态演员表

          is_base_of 与多态无关。

          让每个虚函数定义自己的类型方法是不必要的,因为它是多余的。每个虚拟类已经有一个指向它的虚拟表的指针。

          class Base
          {
           void *p_virtual_table = BASE_VIRTUAL_TABLE;
          }
          
          class Derived : Base
          {
           void *p_virtual_table = DERIVED_VIRTUAL_TABLE;
          }
          
          void *BASE_VIRTUAL_TABLE[n];
          void *DERIVED_VIRTUAL_TABLE[n];
          

          这里的重点是虚拟表的地址是固定的,一个简单的比较将决定一个虚拟对象是否是一个虚拟类的实例。

          由于 cpp 没有为我们提供访问虚拟表的标准方法,因此很难手动进行这些比较。但是 cpp 抽象机在推导出虚拟对象的确切实例时绝对没有问题。

          【讨论】:

          • 另一个使用这种设计模式的现代系统的例子是 LLVM。实际上,在 LLVM 11 及更高版本中,CallBase 不再使用isInvoke() 来确定指令是否为InvokeInst,而是支持isa&lt;InvokeInst&gt;dyn_cast&lt;InvokeInst&gt; 调用。这些使用 LLVM 的 RTTI 版本,因此成本可能与普通的 dynamic_cast&lt;&gt; 不同。更重要的一点是,这种设计模式是一款极其现代且重要的软件的基础,正如@user13947194 所观察到的,虚拟类型函数并不是实现该模式的首选方式。
          【解决方案11】:

          除非您使用 RTTI,否则您只能在编译时使用模板执行此操作。

          它允许您使用 typeid 函数,该函数将产生一个指向 type_info 结构的指针,该结构包含有关类型的信息。

          Wikipedia阅读它

          【讨论】:

          • 投票赞成在这种情况下提及 RTTI,而其他人只是忽略了。
          【解决方案12】:

          您可以使用 模板(或 SFINAE(替换失败不是错误))来实现。示例:

          #include <iostream>
          
          class base
          {
          public:
              virtual ~base() = default;
          };
          
          template <
              class type,
              class = decltype(
                  static_cast<base*>(static_cast<type*>(0))
              )
          >
          bool check(type)
          {
              return true;
          }
          
          bool check(...)
          {
              return false;
          }
          
          class child : public base
          {
          public:
              virtual ~child() = default;
          };
          
          class grandchild : public child {};
          
          int main()
          {
              std::cout << std::boolalpha;
          
              std::cout << "base:       " << check(base())       << '\n';
              std::cout << "child:      " << check(child())      << '\n';
              std::cout << "grandchild: " << check(grandchild()) << '\n';
              std::cout << "int:        " << check(int())        << '\n';
          
              std::cout << std::flush;
          }
          

          输出:

          base:       true
          child:      true
          grandchild: true
          int:        false
          

          【讨论】:

            【解决方案13】:

            在c#中你可以简单地说:

            if (myObj is Car) {
            
            }
            

            【讨论】:

            • 我在发帖人编辑他的问题并指出他的语言选择之前回答了这个问题。
            • 我赞成,OP 指定他的请求不是答案的错。
            【解决方案14】:

            作为多个其他答案的衍生(包括我之前自己发布的一个!),这里有一个宏可以提供帮助:

            #define isInstance( ptr, clazz ) (dynamic_cast<const clazz*>(ptr) != NULL)
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-02-14
              • 1970-01-01
              • 2012-10-09
              • 2015-08-03
              • 1970-01-01
              • 2016-11-24
              • 2018-09-10
              相关资源
              最近更新 更多