C++没有类似 Java 语言的 instanceof 的类型预断,有什么方法可以做到类似 java 的类型预断功能呢?和Java相比,C++要想获得运行时类型信息,只能通过 RTTI (Run Time Type Identification)机制,并且C++最终生成的代码是直接与机器相关的(也就是不同编译器实现RTTI的方式不一样,C++标准只是做了约定)。
RTTI 提供了两个操作符 :typeid 和 dynamic_cast
typeid:返回指针和引用所指的实际类型
dynamic_cast:将基类类型的指针或引用安全地转换为其派生类类型的指针或引用
C++的动态多态是由虚函数实现的,对于多态性的对象,无法在程序编译阶段确定对象的类型。当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。
1、typeid
为了获得一个对象的类型可以使用 typeid 操作,该操作返回一个 type_info 类对象的引用,type_info 类的定义在文件<typeinfo>中。
这里先介绍一下type_info源码:
class type_info
{
public:
/** Destructor first. Being the first non-inline virtual function, this
* controls in which translation unit the vtable is emitted. The
* compiler makes use of that information to know where to emit
* the runtime-mandated type_info structures in the new-abi. */
virtual ~type_info();
/** Returns an @e implementation-defined byte string; this is not
* portable between compilers! */
const char* name() const _GLIBCXX_NOEXCEPT
{ return __name[0] == '*' ? __name + 1 : __name; }
#if !__GXX_TYPEINFO_EQUALITY_INLINE
// In old abi, or when weak symbols are not supported, there can
// be multiple instances of a type_info object for one
// type. Uniqueness must use the _name value, not object address.
bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT;
bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT;
#else
#if !__GXX_MERGED_TYPEINFO_NAMES
/** Returns true if @c *this precedes @c __arg in the implementation's
* collation order. */
// Even with the new abi, on systems that support dlopen
// we can run into cases where type_info names aren't merged,
// so we still need to do string comparison.
bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return (__name[0] == '*' && __arg.__name[0] == '*')
? __name < __arg.__name
: __builtin_strcmp (__name, __arg.__name) < 0; }
bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{
return ((__name == __arg.__name)
|| (__name[0] != '*' &&
__builtin_strcmp (__name, __arg.__name) == 0));
}
#else
// On some targets we can rely on type_info's NTBS being unique,
// and therefore address comparisons are sufficient.
bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return __name < __arg.__name; }
bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return __name == __arg.__name; }
#endif
#endif
#if __cpp_impl_three_way_comparison < 201907L
bool operator!=(const type_info& __arg) const _GLIBCXX_NOEXCEPT
{ return !operator==(__arg); }
#endif
#if __cplusplus >= 201103L
size_t hash_code() const noexcept
{
# if !__GXX_MERGED_TYPEINFO_NAMES
return _Hash_bytes(name(), __builtin_strlen(name()),
static_cast<size_t>(0xc70f6907UL));
# else
return reinterpret_cast<size_t>(__name);
# endif
}
#endif // C++11
// Return true if this is a pointer type of some kind
virtual bool __is_pointer_p() const;
// Return true if this is a function type
virtual bool __is_function_p() const;
// Try and catch a thrown type. Store an adjusted pointer to the
// caught type in THR_OBJ. If THR_TYPE is not a pointer type, then
// THR_OBJ points to the thrown object. If THR_TYPE is a pointer
// type, then THR_OBJ is the pointer itself. OUTER indicates the
// number of outer pointers, and whether they were const
// qualified.
virtual bool __do_catch(const type_info *__thr_type, void **__thr_obj,
unsigned __outer) const;
// Internally used during catch matching
virtual bool __do_upcast(const __cxxabiv1::__class_type_info *__target,
void **__obj_ptr) const;
protected:
const char *__name;
explicit type_info(const char *__n): __name(__n) { }
private:
/// Assigning type_info is not supported.
type_info& operator=(const type_info&);
type_info(const type_info&);
};
可以看到 type_info 类的构造函数/拷贝构造都为私有函数,也就是不允许用户自己来创建type_info类对象。该类常用来判断类型的方法就是 operator()== 和 operator()!= 。
下面测试代码演示怎么使用 typeid ,注意:必须借助 虚函数表 来实现类型推断!
#include <iostream>
#include <functional>
using namespace std;
class Base{
public:
virtual
void test(){
cout << "Base::test" << endl;
}
};
class Derived : public Base{
public:
void test(){
cout << "Derived::test" << endl;
}
virtual
~Derived(){
cout << "Derived::~Derived" << endl;
}
};
int main()
{
Base base;
Derived derive;
Derived derive2;
Base* pBase = new Base();
Base* pBase2 = new Derived();
Derived* pDerive = new Derived();
//base和derive类型不同,返回false
cout << "typeid(base) == typeid(derive) : " << (typeid(base) == typeid(derive)) << endl;
//derive和derive2类型相同,返回true
cout << "typeid(derive2) == typeid(derive) : " << (typeid(derive2) == typeid(derive)) << endl;
//base和derive类型不同,返回false
cout << "typeid(&base) == typeid(&derive) : " << (typeid(&base) == typeid(&derive)) << endl;
//derive和derive2类型相同,返回true
cout << "typeid(&derive2) == typeid(&derive) : " << (typeid(&derive2) == typeid(&derive)) << endl;
//pBase和pDerive指向对象类型不同,返回false
cout << "typeid(pBase) == typeid(pDerive) : " << (typeid(pBase) == typeid(pDerive)) << endl;
//pBase2和pDerive指向对象类型相同,返回true
cout << "typeid(pBase2) == typeid(pDerive) : " << (typeid(*pBase2) == typeid(*pDerive)) << endl;
return 0;
}
大家可以测试一下,将 virtual 关键字注释掉再试试,类型推断结果会不一样!以下为output信息:
有虚函数的情况:
typeid(base) == typeid(derive) : 0
typeid(derive2) == typeid(derive) : 1
typeid(&base) == typeid(&derive) : 0
typeid(&derive2) == typeid(&derive) : 1
typeid(pBase) == typeid(pDerive) : 0
typeid(pBase2) == typeid(pDerive) : 1
没有虚函数的情况:
typeid(base) == typeid(derive) : 0
typeid(derive2) == typeid(derive) : 1
typeid(&base) == typeid(&derive) : 0
typeid(&derive2) == typeid(&derive) : 1
typeid(pBase) == typeid(pDerive) : 0
typeid(pBase2) == typeid(pDerive) : 0
为什么会这样呢???
前面咱们是不是讲到 virtual 是实现动态多态的手段,如果没有虚函数表,那么就可以在编译时根据操作内容推导出类型信息,以下静态类型都能在编译阶段确定类型信息:
- 类型名(例如:int、Base)
- 一个基本类型的变量(例如:2、3.0f)
- 一个具体的对象(例如:Base base;)
- 一个指向无虚函数表的类对象的指针
所以,当你对 typeid(pBase) 和 typeid(pBase2) 进行类型推导时,由于Base类中并不存在虚函数表,最终实际上和 typeid(Base*) 的效果一样!也就是在编译时就能够确定操作数的类型!
那么 typeid 是怎么动态推导类型的呢?那肯定是和咱们的虚函数表有关系!前面咱们讲过,typeid返回的是type_info类对象的引用,也就是只要是同一个类的实例,typeid 的返回值应该都是相同的!
//typeid(pBase2) 和 typeid(pDerive) 返回地址相同
cout << "typeid(pBase2) = " << &typeid(*pBase2) << " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
在main函数中添加以上测试代码,(注意将 Base 类中的 virtual 关键字注释取消,因为咱们要测试动态类型推导时的返回地址),此时,测试代码输出的日志信息为:
typeid(pBase2) = 0x564006a8ad20 typeid(pDerive) = 0x564006a8ad20
typeid 返回的地址相同,所以你去比较这两个对象的类型肯定也是相同的!
注意:此时你注释掉virtual看看,是否还相同呢?想想为什么?
typeid 的原理咱们先讲到这,如果你是C++编译器开发者,如果让你去实现RTTI技术,你如何去确定一个对象的真实类型呢?
首先,每个类在编译之后应该有一个 type_info 数据结构来标识这个类的唯一性,这个 type_info 数据应该是确定的并在编译时就创建出来了。
其次,typeid 的实现;显示 typeid 的功能无非考虑两种情况:
- 静态:直接根据操作数(例如int、无虚函数表的类对象等)找到对应类型的 type_info 数据即可。
- 动态:此时想要返回对象的真实类型只能够借助虚函数表了,那么,虚函数表中应该能找到真实类型的 type_info 数据的入口(类似虚函数表存储的是虚函数的地址,这里应该也是存储的是指向 type_info 数据的地址)。
ok,讲到这里,我们就需要深入挖掘虚函数表相关的知识,那么你要先补充一下虚函数表相关知识。typeid 的动态类型推导与虚函数表的关系,咱们放到下一篇在来讲!
Tips:可以先看看我的new[]/delete[],原理相通!
2、dynamic_cast
dynamic_cast运算符将一个指向基类的指针转换成指向派生类的指针;如果失败,返回空指针。注意,主要用来进行向上转换,也就是从父类指针转换成子类指针(用来实现c++多态的),因为子类指针是不需要进行转换就可以被赋值给父类指针的!
好了嘛,既然是要实现多态,那么这里就必须借助虚函数表!也就是dynamic_cast在操作无虚函数表的对象时是不允许的!(会抛异常/编译错误)
添加以下测试代码:
//将父类指针转为子类指针
auto p1 = dynamic_cast<Derived*>(pBase);
//将指向子类的父类指针转为子类指针
auto p2 = dynamic_cast<Derived*>(pBase2);
cout << "p1 = " << p1 << " p2 = " << p2 << endl;
日志输出:
p1 = 0 p2 = 0x556f565f4ed0
至此,咱们就讲解完了C++中RTTI技术中两个操作符的使用方式和原理!后面一篇咱们再对typeid和虚函数的关系做更进一步讲解。
后一篇地址:typeid 和虚函数_master-计算机科学专栏-CSDN博客
有问题请留言,欢迎转发,请勿拷贝!