C++ Primer学习笔记 - 运行时类型识别
运行时类型识别(run-time type idenfitication,RTTI)的功能由2个运算符实现:
1)typeid,用于返回表达式的类型;
2)dynamic_cast,用于将base class pointer(或reference)安全转型为derived class pointer(或reference)。
TIPS:使用RTII时,应尽可能定义virtual函数,而非non-virtual函数。当调用virtual函数时,编译器会根据对象的动态类型自动地选择正确的函数版本。
dynamic_cast运算符
使用方法
// type必须是类型
dynamic_cast<type*>(e) // e必须是一个指针
dynamic_cast<type&>(e) // e必须是左值
dynamic_cast<type&&>(e) // e是右值
当e的类型满足以下3种条件之一时:1)是type本身,2)public base class,3)public derived class时,则转型可以成功;否则,转型失败。
当转换失败时,如果转换目标是指针,结果为0;如果转换目标是引用,则抛出bad_cast异常。
注:不能是private base/derived class,因为private继承代表has-a关系,而非public继承的is-a关系。
dynamic_cast指针转型
对于有public继承关系Base class和Derived class
class Base {
public:
virtual void func1() { cout << "Base::func1()" << endl; }
void func2() { cout << "Base::func2()" << endl; }
};
class Derived : public Base {
public:
virtual void func1() { cout << "Derived::func1()" << endl; }
void func2() { cout << "Derived::func2()" << endl; }
};
使用dynamic_cast这样对指针进行转型:
// 转型成功
Base* bp = new Derived;
if (Derived *dp = dynamic_cast<Derived*>(bp)) { // 转型
// 使用dp指向Derived对象
cout << "dp is not null" << endl; // 打印"dp is not null"
}
else {
// bp指向base对象
cout << "dp is null" << endl;
}
// 转型失败
Base* bp2 = new Base;
if (Derived *dp = dynamic_cast<Derived*>(bp2)) { // 转型
// 使用dp指向Derived对象
cout << "dp is not null" << endl;
}
else {
// bp指向base对象
cout << "dp is null" << endl; // 打印"dp is null"
}
注:对空指针进行dynamic_cast转型,结果是所需类型的空指针。
dynamic_cast引用转型
与指针转型不同的是,引用不存在空引用的情况,转型失败时直接抛出std::bad_cast异常(定义在
void func(const Base &b)
{
try {
const Derived &d = dynamic_cast<const Derived&>(b); // 转型,注意dynami_cast转型无法丢掉const常量性
cout << "b is Derived object" << endl;
}
catch (bad_cast) {
cout << "b is Base object" << endl;
}
}
int main()
{
Base b;
func(b); // 打印"b is Base object"
Derived d;
func(d); // 打印"b is Derived object"
return 0;
}
[======]
typeid运算符
typeid运算符 运行程序向表达式提问:你的对象是什么类型?
typeid用法
typeid(e); // 运算符对象e,可以是任意表达式或类型的名字
// typeid 操作符结果是一个常量对象的引用,对象的类型是标准库类型type_info或其public派生类
typeid运算符的对象e可用是任意表达式,或者类型的名字,顶层const会被忽略。
如果e是引用,typeid返回所引用对象的类型;
如果e是数组或函数时,不会执行指针的标准类型转换,仍然是数组类型或者函数类型,而非指针类型;
如果e不属于类类型或者是一个不包含任何virtual函数的类时,typeid运算符指示的运算对象是静态类型。只有当运算符对象是 定义了至少一个virtual函数的类的左值对象时,typeid的结果直到运行时才会求得;否则,编译期就返回静态类型。
typeid的应用
typeid常用于比较2个表达式类型是否相同,或者比较一条表达式是否与指定类型相同
class Base {
public:
virtual void func1() { cout << "Base::func1()" << endl; } // 包含了1个virtual函数
void func2() { cout << "Base::func2()" << endl; }
};
class Derived : public Base {
public:
virtual void func1() { cout << "Derived::func1()" << endl; }
void func2() { cout << "Derived::func2()" << endl; }
};
int main()
{
Derived *dp = new Derived;
Base *bp = dp;
if (typeid(bp) == typeid(dp)) { // 不通过:比较Base*和Derived*,将在编译期返回静态类型
cout << "bp, dp own same type" << endl; // 不执行
}
if (typeid(*bp) == typeid(*dp)) { // 通过:比较Base对象和Derived对象,将在运行期求值
cout << "*bp, *dp own same type" << endl; // 执行
}
if (typeid(*bp) == typeid(Derived)) { // 通过:比较Base对象和Derived对象,将在运行期求值
cout << "*bp, Derived own same type" << endl; // 执行
}
if (typeid(bp) == typeid(Derived)) { // 不通过:比较Base*和Derived,将在编译期返回静态类型
cout << "bp, Derived own same type" << endl; // 不执行
}
return 0;
}
[======]
运行时类型识别RTTI技术
何为两个对象相等?
对于2个对象,如果它们的\(\color{red}{类型相同}\)并且\(\color{red}{对应的数据成员相同}\),我们则称这2个对象是相等的。
在继承体系中,除了继承而来的数据成员,派生类还有自己负责添加的数据成员,因此,派生类的相等运算符(operator==)必须把派生类的新成员考虑进来。
一种判断对象是否相等的方案:定义一套virtual函数,在继承体系上的各个层次上分布执行相等性判断。为此,可以为base class定义一个相等运算符,可以将其工作委托给virtual函数equal。
上述方案存在的问题:virtual函数的base class版本和derived class版本必须具有相同的形参类型,否则无法通过base pointer/reference动态调用derived object的virtual函数。如果想定义virtual equal函数,参数必须是base class reference,因为base class并不知道derived class的存在,而equal判断是否相等如果用copy对象,就失去了意义。因此其函数参数必须是base class reference或pointer,那为什么是reference而不是pointer呢?因为参数不允许为null,跟null对象比较没有意义。
思考一个问题:如果两个对象的类型不一样,这2个对象相等吗?
答案是否定的,肯定不相等。这也就是说,比较2个对象是否相等时,如果排除了类型不同的情况,那么两个对象类型相同,而判断同类型对象是否相等,只需要借助对象的virtual函数equal比较数据成员(自身添加的以及继承而来的数据成员)即可。
于是,可以写出判断2个对象是否相等函数的示例:
class Base {
// 并没有将operator==定义为成员函数,是因为不必要是成员函数。effective C++条款23告诉我们,这样可以增加类的封装性
friend bool operator==(const Base&, const Base&);
public:
// Base的接口成员
protected:
virtual bool equal(const Base&) const;
private:
// Base的数据成员
};
bool operator==(const Base& lhs, const Base& rhs)
{
// 如果typeid不相同,返回false;否则调用equal比较数据成员
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
class Derived : public Base {
public:
// Derived的接口成员
protected:
virtual bool equal(const Base&) const;
private:
// Derived的数据成员
};
bool Derived::equal(const Base& rhs) const
{
auto r = dynamic_cast<const Derived&>(rhs);
// 执行比较2个Derived对象操作并返回结果
if (all data == all data of rhs)
return true;
else return false;
}
type_info类
type_info类准确定义不同编译器略有不同。C++标准要求typeinfo类定义在
t1 == t2 | 如果type_info对象t1,t2是同一种类型,就返回true;否则,返回false |
t1 != t2 | 如果type_info对象t1, t2不是同一种类型,就返回true;否则,返回false |
t.name() | 返回一个C风格字符串,表示类型的名字 |
t1.before(t2) |
type_info类一般作为base class出现,当编译器希望提供额外的类型信息时,通常在type_info的derived class中完成。
可以从type_info的源码中看到,它没有默认构造函数,也删除了copy函数(copy构造、copy assignment运算符)。无法定义或拷贝type_info对象,也不能赋值。创建type_info类对象唯一途径是使用typeid运算符(前面有提到typeid运算符返回 表示表达式类型的值)
注意:C++ Primer 5th提到,type_info删除了移动构造函数,这点是通过删除private成员__std_type_info_data的移动构造函数实现,而非通过type_info声明自身的移动构造函数为delete。同样的,无法为type_info合成default构造函数,因为private成员__std_type_info_data的default构造函数已经被删除。
// 以下代码来自VS 2017 type_info定义
class type_info
{
public:
type_info(const type_info&) = delete;
type_info& operator=(const type_info&) = delete;
virtual ~type_info() noexcept;
...
private:
mutable __std_type_info_data _Data;
}
type_info的用法
如何利用type_info类打印类型信息?
type_info类的name成员函数返回一个C风格字符串,表示对象的类型名称。具体的名称字符串,取决于不同的编译器实现(可查看编译器手册),不一定与程序员编写的类型信息一样。
例,
int arr[10];
Derived d;
Base* p = &d;
cout << typeid(52).name() << endl;
cout << typeid(arr).name() << endl;
cout << typeid(std::string).name() << endl;
cout << typeid(p).name() << endl;
cout << typeid(*p).name() << endl;
MSVC 2017运行结果:
int
int [10]
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
class Base *
class Derived