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

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

173560 次浏览

In c# you can simply say:

if (myObj is Car) {


}

You can only do it at compile time using templates, unless you use RTTI.

It lets you use the typeid function which will yield a pointer to a type_info structure which contains information about the type.

Read up on it at Wikipedia

You really shouldn't. If your program needs to know what class an object is, that usually indicates a design flaw. See if you can get the behavior you want using virtual functions. Also, more information about what you are trying to do would help.

I am assuming you have a situation like this:

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


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

If this is what you have, then try to do something like this:

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

Edit: Since the debate about this answer still goes on after so many years, I thought I should throw in some references. If you have a pointer or reference to a base class, and your code needs to know the derived class of the object, then it violates Liskov substitution principle. Uncle Bob calls this an "anathema to Object Oriented Design".

 

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

You can do it with dynamic_cast (at least for polymorphic types).

Actually, on second thought--you can't tell if it is SPECIFICALLY a particular type with dynamic_cast--but you can tell if it is that type or any subclass thereof.

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

dynamic_cast can determine if the type contains the target type anywhere in the inheritance hierarchy (yes, it's a little-known feature that if B inherits from A and C, it can turn an A* directly into a C*). typeid() can determine the exact type of the object. However, these should both be used extremely sparingly. As has been mentioned already, you should always be avoiding dynamic type identification, because it indicates a design flaw. (also, if you know the object is for sure of the target type, you can do a downcast with a static_cast. Boost offers a polymorphic_downcast that will do a downcast with dynamic_cast and B0 in debug mode, and in release mode it will just use a static_cast).

I don't know if I understand your problem correctly, so let me restate it in my own words...

Problem: Given classes B and D, determine if D is a subclass of B (or vice-versa?)

Solution: Use some template magic! Okay, seriously you need to take a look at LOKI, an excellent template meta-programming library produced by the fabled C++ author Andrei Alexandrescu.

More specifically, download LOKI and include header TypeManip.h from it in your source code then use the SuperSubclass class template as follows:

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

According to documentation, SuperSubClass<B,D>::value will be true if B is a public base of D, or if B and D are aliases of the same type.

i.e. either D is a subclass of B or D is the same as B.

I hope this helps.

edit:

Please note the evaluation of SuperSubClass<B,D>::value happens at compile time unlike some methods which use dynamic_cast, hence there is no penalty for using this system at runtime.

I disagree that you should never want to check an object's type in C++. If you can avoid it, I agree that you should. Saying you should NEVER do this under any circumstance is going too far though. You can do this in a great many languages, and it can make your life a lot easier. Howard Pinsley, for instance, showed us how in his post on C#.

I do a lot of work with the Qt Framework. In general, I model what I do after the way they do things (at least when working in their framework). The QObject class is the base class of all Qt objects. That class has the functions isWidgetType() and isWindowType() as a quick subclass check. So why not be able to check your own derived classes, which is comparable in it's nature? Here is a QObject spin off of some of these other posts:

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

And then when you are passing around a pointer to a QObject, you can check if it points to your derived class by calling the static member function:

if( MyQObject::isThisType( qObjPtr ) ) qDebug() << "This is a MyQObject!";
#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;
}
}

Result:

IS A D22
IS A D2
IS A Base

I was thinking along the lines of using typeid()...

Well, yes, it could be done by comparing: typeid().name(). If we take the already described situation, where:

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


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

A possible implementation of foo(Base *p) would be:

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

The code below demonstrates 3 different ways of doing it:

  • virtual function
  • typeid
  • 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;
}

The program above prints this:

type A
type A
type A

You can do it with templates (or SFINAE (Substitution Failure Is Not An Error)). Example:

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

Output:

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

I see some good answers here and I see some dumb response.

"Trying to query the type of an object is a design flaw". Which means that Java instanceof and C# is keywords are design flaws. These are response of people that dont rate polymorphism. If you have an interface, that interface is derived by another interface that impelments more features. If you need these extra features you must first check that you have such an interface. Even microsoft COM API makes use of this design.

Then in terms of how to deduce if an object is a instanceof a class, many good answers have already been given

  • typeid
  • having a virtual type function
  • dynamic cast

is_base_of has nothing to do with polymorphism.

And having each virtual function define its own type method is unnecessary as it is redundant. Each virtual class already has a pointer to its virtual table.

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

The point here is that the address of the virtual tables are fixed and a simple comparrison will decide if a virtual object is an instanceof a virtual class.

Since cpp doesnt give us a standard way of accessing the virtual tables, it would be hard to do these comparrisons manually. But the cpp abstract machine has absolutely no problems deducing the exact instance of a virtual object.

As a spin off of multiple other answers (including one I previously posted myself!), here's a macro to help:

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