98

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

4

14 回答 14

141

 

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;
  }
}
于 2008-11-21T04:01:30.380 回答
50

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

我假设你有这样的情况:

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 替换原则Bob 大叔将此称为“对面向对象设计的诅咒”。

于 2008-11-21T03:57:27.000 回答
33

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

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

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}
于 2008-11-21T04:05:46.517 回答
8

下面的代码演示了 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
于 2019-07-10T19:39:58.993 回答
6

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

于 2008-11-21T04:49:14.400 回答
5

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

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

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

更具体地说,在您的源代码中下载LOKITypeManip.h并从中包含标头,然后使用SuperSubclass类模板,如下所示:

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

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

即要么D是 的子类,B要么D与 相同B

我希望这有帮助。

编辑:

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

于 2008-11-21T16:43:50.340 回答
4

我不同意您永远不想在 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!";
于 2015-12-19T21:31:12.200 回答
4
#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
于 2017-05-26T17:00:59.740 回答
2

我在考虑使用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
    }
}
于 2017-06-06T22:31:34.963 回答
2

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

“试图查询对象的类型是一个设计缺陷”。这意味着Java instanceof 和C# 是关键字是设计缺陷。这些是不评价多态性的人的反应。如果您有一个接口,则该接口是由另一个实现更多功能的接口派生的。如果您需要这些额外的功能,您必须首先检查您是否有这样的接口。甚至微软的 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 抽象机在推断虚拟对象的确切实例时绝对没有问题。

于 2021-06-07T06:04:08.643 回答
1

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

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

在维基百科上阅读它

于 2008-11-21T03:55:55.040 回答
1

您可以使用模板(或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
于 2020-12-20T13:16:26.117 回答
0

在 c# 中,您可以简单地说:

if (myObj is Car) {

}
于 2008-11-21T03:49:24.663 回答
0

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

#define isInstance( ptr, clazz ) (dynamic_cast<const clazz*>(ptr) != NULL)
于 2021-11-04T21:00:14.243 回答