什么是RTTI
?
- Run Time Type Information
- RTTI是一种可以在运行时推导出对象类型的机制
- 它可以在运行时暴露有关于对象类型的信息,并且仅仅适用于至少有一个虚函数的类。
一、 typeinfo and type_id
众所周知,面向对象的三大特征是封装、继承、多态。
多态指的是一个基类类型的指针,可以指向其派生类对象,例如Base *ptr = new Derive()
。
在这种情况下,我们如何才能知道ptr
指向得对象的类型信息?这就是C++中typeid
存在的原因了
注意
typeid(expr)
,expr
一定是一个包含虚函数的类
#include <iostream>
#include <typeinfo>
struct Base {
virtual void vvfunc() {}
};
struct Derived : public Base {};
using namespace std;
int main(int argc, char *argv[]) {
Derived *pd = new Derived;
Base *pb = pd;
cout << typeid(pb).name() << endl; // P4Base
cout << typeid(*pb).name() << endl; // 7Derived
cout << typeid(pd).name() << endl; // P7Derived
cout << typeid(*pd).name() << endl; // 7Derived
delete pd;
return 0;
}
P
表示“指针”, 数字7或者4代表乐了名的长度。
#include <cassert>
#include <cstring>
#include <iostream>
#include <typeinfo>
using namespace std;
class A {
private:
int a;
};
class B : public A {
public:
virtual void f() {}
};
class C : public B {
public:
virtual void f() {}
};
int main() {
A *pa = new B();
B *pb = new C();
assert(strcmp(typeid(*pa).name(), "1A") == 0);
assert(strcmp(typeid(*pb).name(), "1C") == 0);
}
因为A
不是多态类型(没有虚函数),所以typeid(*pa)
返回的是A
的类型信息,而不是B
的类型信息。
此外,我们还可以对静态类型执行typeid
。表达式的静态类型是指编译时已知的表达式类型。
#include <typeinfo>
int main()
{
typeid(int) == typeid(int&); // evaluates to true
}
typeid()
也可以用于模板类型的推导:
#include <typeinfo>
template <typename T>
T max(T arg1, T arg2) {
cout << typeid(T).name() << "s compared." << endl;
return ( arg1 > arg2 ? arg1 : arg2 );
}
二、 dynamic_cast
在C
语言里面我们可以随便转化任意类型的指针,在C++
中,dynamic_cast
能够帮助我们进行一些运行时的多态类型检查。如果转化是不安全的,就会抛出bad_cast
异常或者返回空指针。
转化引用失败的话会抛出异常,转化指针失败的话会返回
nullptr
#include <iostream> // std::cout
#include <typeinfo> // std::bad_cast
class Base { virtual void member() {} };
class Derived : Base {};
int main() {
try {
Base b;
Derived &rd = dynamic_cast<Derived &>(b);
} catch (std::bad_cast &bc) {
std::cerr << "bad_cast caught: " << bc.what() << '\n'
}
return 0;
}
输出:
bad_cast caught: std::bad_cast
2.1 经典使用场景
下面是一个典型的dynamic_cast的应用案例:
#include <cassert>
#include <iostream>
#include <stdio.h>
struct A {
virtual void test() { printf("in A\n"); }
};
struct B : A {
virtual void test() { printf("in B\n"); }
void test2() { printf("test2 in B\n"); }
};
struct C : B {
virtual void test() { printf("in C\n"); }
void test2() { printf("test2 in C\n"); }
};
void Globaltest(A &a) {
try {
C &c = dynamic_cast<C &>(a);
printf("in GlobalTest\n");
} catch (std::bad_cast) {
printf("Can't cast to C\n");
}
}
int main() {
A *pa = new C;
A *pa2 = new B;
pa->test(); // in C
pa2->test(); // in B
B *pb = dynamic_cast<B *>(pa);
if (pb)
pb->test2(); // test2 in B
/* pc is nullptr, since the object B knows nothing about C,
* even if this object is pointed by `A*`.
*/
C *pc = dynamic_cast<C *>(pa2);
assert(pc == nullptr);
C ConStack;
Globaltest(ConStack); // in GlobalTest
/* will fail because B knows nothing about C */
B BonStack;
Globaltest(BonStack); // Can't cast to C
}
三、 const_cast
const_cast
主要用于移除const
和volatile
等修饰符号。
#include <boost/type_index.hpp>
#include <iostream>
using namespace std;
using boost::typeindex::type_id_with_cvr;
int main() {
const int *p = new int[1];
cout << type_id_with_cvr<decltype(p)>().pretty_name() << endl; // const int*
cout << type_id_with_cvr<decltype(const_cast<int*>(p))>().pretty_name() << endl; // int*
delete[] p;
}
在类中还可以这样
#include <boost/type_index.hpp>
#include <iostream>
using boost::typeindex::type_id_with_cvr;
using namespace std;
class CCTest {
public:
void printType() const {
cout << type_id_with_cvr<decltype(this)>().pretty_name() << endl; // const CCTest *
cout << type_id_with_cvr<decltype(const_cast<CCTest *>(this))>().pretty_name()<< endl; // CCTest *
}
private:
int number;
};
int main() {
CCTest X;
X.printType();
}
四、 reinterpret_cast
reinterpret_cast
主要用于没有血缘关系的类型指针之间进行转化, 主要用于内存空间的转化, 比如将一个内存页转化为整型数组
#include <iostream>
#include <cstdint>
using namespace std;
// Returns a hash code based on an address
unsigned short Hash(void *p) {
uint64_t val = reinterpret_cast<uint64_t>(p);
return (unsigned short)(val ^ (val >> 16));
}
using namespace std;
int main() {
int a[20];
for (int i = 0; i < 20; i++)
cout << Hash(a + i) << endl;
}
五、 static_cast
static_cast
使用的是c++
默认的类型转化, 可以满足派生类向基础类的转化, 但不进行类型检查, 所以存在一定的风险,如果没有RTTI
就只能够使用static_cast
进行多态转化。但是static_cast
不能够用于没有血缘关系的类型指针之间转化。static_cast
更像是补充的作用,前面几种作用不到的场景,static_cast
来做。
int a = 1;
float f = static_cast<float>(a); // ok
int* ip = &a;
float *if = static_cast<float*>(ip); // error
多态类转化:
#include <iostream>
using namespace std;
class B {
virtual void f() {}
};
class D : public B {
public:
int val = 123;
void f2() { printf("D::f2(), val = %d\n", val); }
};
void f(B *pb, D *pd) {
D *pd2 = static_cast<D *>(pb); // Not safe, D can have fields and methods that are not in B.
pd2->f2(); // val is an uncertain value
pd2->val = 233; // this may damage other data
B *pb2 = static_cast<B *>(pd); // Safe conversion, D always contains all of B.
}
int main() {
B b;
D d;
f(&b, &d);
}