如何检查对象的类型是否是C ++中的特定子类?


79

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


我只是想知道是否有一种方法可以在C ++编译时检查对象的类型是否是特定的子类,因为它std::is_base_of无法按需工作。:3
KaiserKatze

Answers:


37

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

我假设您遇到这样的情况:

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替换原理鲍伯叔叔将此称为“面向对象设计的厌恶品”。


20
+1。我认为正确的名称是“告诉,不要问”。基本上,始终要在要求询问以查找要处理的对象类型的case / if语句中,多态性(告诉对象要做什么,让实现由它来处理)。
LeopardSkinPillBoxHat

61
是的-一切都很好-但是那家伙想知道如何解析类型
-JohnIdol

7
@Dima,如果有人想仅仅出于学习目的而知道语法(假设他们正在阅读一本用Java编写的书,讲述设计缺陷,他们需要将其翻译为C ++)怎么办?
拼凑而成的

8
@Dima是否曾与定义超类的外部库一起工作?请尝试在此处应用答案。
托马什Zato -恢复莫妮卡

13
这个答案做出了一个相当大的假设,即您可以控制需要转换为的类型并可以重写它们。例如,我正在基于另一个GUI库向GUI库添加功能,我需要知道是否小部件的父级是可滚动的。原始库没有提供测试方法,因此我必须尝试将我的小部件强制转换为可滚动小部件的基类,这确实很糟糕。无论如何,关键是您忽略了手头问题的实际答案。
AnorZaken

124

 

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

1
您真的需要dynamic_cast<>这里吗?还static_cast<>不够吗?
krlmlr

15
@krlmlr。你能x在编译时告诉类型吗?如果是这样,那static_cast<>()将工作。如果您x直到运行时才能知道类型,那么您需要dynamic_cast<>()
Martin York

谢谢。我主要在CRTP中使用
低调广播

好的答案,但这里需要注意。三元条件运算符要求其第二和第三操作数具有相同的类型。因此,idk如何以这种方式对任何人有效,请改用if / else。也许这在过去有用吗?无论如何。
Nikos

@Nikos,之所以起作用,是因为:1. C ++不需要三进制的大小写是相同的类型,2.它们是派生类指针的类型,并且派生类指针隐式转换为基数。
hazer_hazer

30

您可以使用它dynamic_cast(至少对于多态类型)。

实际上,经过深思熟虑-您无法确定它是否是特定类型-但dynamic_cast可以确定它是否是该类型或其任何子类。

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

子类何时不是多态类型?
OJFord

6
@OllieFord:没有任何虚函数时。
德鲁·霍尔

换句话说,何时std::is_polymorphic_v<T>false
Xeverous

7

下面的代码演示了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

6

dynamic_cast可以确定类型是否在继承层次结构中的任何位置包含目标类型(是的,这是一个鲜为人知的功能,如果B继承自AC,它可以将anA*直接转换为C*)。typeid()可以确定对象的确切类型。但是,都应极其谨慎地使用它们。如前所述,您应该始终避免动态类型识别,因为它表明了设计缺陷。(另外,如果你知道对象是为确保目标的类型,你可以用一个垂头丧气static_cast。升压提供一个polymorphic_downcast,将做一个沮丧的用dynamic_castassert在调试模式下,并在释放模式将只使用一个static_cast)。


4

我不同意您永远不应该在C ++中检查对象的类型。如果您可以避免,我同意您应该这样做。说无论如何在任何情况下都不要这样做,这太过分了。您可以使用多种语言来完成此操作,这可以使您的生活更加轻松。例如,霍华德·平斯利(Howard Pinsley)在他关于C#的帖子中向我们展示了如何。

我在Qt Framework上做了很多工作。通常,我会按照他们的工作方式(至少在他们的框架中工作时)对我的工作进行建模。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!";

4

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

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

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

更具体地说,下载LOKI并将其标头包含TypeManip.h在源代码中,然后SuperSubclass按如下方式使用类模板:

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

根据文档,SuperSubClass<B,D>::value如果B是的公共基础D,或者如果BD是相同类型的别名,则为true 。

D是的子类BD与相同B

我希望这有帮助。

编辑:

请注意,SuperSubClass<B,D>::value与某些使用的方法不同,在编译时评估发生的情况dynamic_cast,因此在运行时使用该系统不会有任何损失。


3
#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

1

除非使用RTTI,否则只能在编译时使用模板来完成。

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

Wikipedia上阅读它


在这种情况下投票赞成提及RTTI,其他所有人只是忽略了。
ManuelSchneid3r '16


1

我一直在考虑使用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
    }
}

1
为什么混合使用typeid()。name()和typeid()的比较?为什么不总是比较typeid()?
Silicomancer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.