运行时类型检查

来源:互联网 发布:日语汉字读音软件 编辑:程序博客网 时间:2024/05/01 00:24
运行时类型信息(run-time type information)通常记做RTTI。
在C++primer第五版中,译作运行类型识别(run-time type identification)。

运行时类型信息包括三部分:
1)一个运算符dynamic_cast,给它一个指向某某对象的基类指针,它能得到一个到这个对象的派生类指针。只有在被指对象确实属于所指明的派生类时,运算符dynamic_cast才能返回一个指针,否则就返回0;
2)一个运算符typeid,它对一个给定的基类指针识别出被指对象的确切类型。
3)一个结构type_info,作为与有关类型的更多运行时类型信息的挂接点(hook)

这个机制特别适用于    想使用基类对象的的指针或引用执行某个派生类的操作并且该操作不是虚函数    的时候。一般来说,能用虚函数就用虚函数。到操作被定义成虚函数时,编译器将根据对象的动态类型自动的选择正确的函数版本。
使用RTTI的是时候必须清楚地知道转换的目标类型并且必须检查类型转换是否被正确执行。

dynamic_cast运算符:
当我们想安全的使用派生类的某些细节时,为了使用这个派生类,我们需要获得一个指向派生类的指针。通常情况下,我们并不关心被指向对象的确凿类型,我们只需要知道我们能不能安全地做这个强制。这时我们可以通过dynamic_cast运算符来实现。
例如:
#include <iostream>class Dialog{public : virtual void print() = 0;};class Dialog_box : public Dialog{public : virtual void print() {  std::cout << "this is class Dialog_box" << std::endl; } virtual int ask() {  return 0; }};class DBox_w_str : public Dialog_box{public: virtual void print() {  std::cout << "this is class DBox_w_str" << std::endl; } int ask() {  return 0; } virtual char *get_string() {  return nullptr; }};void My_func(Dialog_box *bp){ if(DBox_w_str *dbp = dynamic_cast<DBox_w_str *>(bp)) {  dbp->print(); } else {  //treat *bp as a "plain" dialog box }}int main(){ DBox_w_str *dbp = new DBox_w_str; My_func(dbp); system("PAUSE"); return 0;}


就一般而言,如果*p确实是一个T或者由T派生的类的对象,在这里的dynamic_cast<T *>(p)运算符就会将其运算对象转换成所需要的T*类型;否则dynamic_cast<T *>(p)将是0;特别的,我们可以将空指针强制到所需类型的空指针。
当然,我们也可以通过作用dynamic_cast运算符把对象强制到某个引用类型。如果强制失败就会抛出一个bad_cast异常。
例如:
void My_func(Dialog_box &bp)
{
 if(DBox_w_str &dbp = dynamic_cast<DBox_w_str &>(bp))
 {
  dbp->print();
 }

将测试和强制合并为一个操作有很多优点:
    1.    动态强制使测试和强制之间不会出现匹配错误。
    2.    通过利用在对象里的可用的类型信息,我们可能将它转换到这个强制的作用域里看不到完整定义的某个类型。
    3.    通过利用在对象里可用的类型信息,我们经常可以从一个虚的基类强制到一个派生类中去。
    4.    静态的强制不能对所有的这些情况都给出正确的结果。

typeid运算符和type_info类:
typeid运算符的出现主要是为了满足下面两个需求:
1)可能需要一个确定对象的确切类型。也就是说,告诉程序员这个对象就是X类的对象,而不是说,它是X类的或者某个由X类派生的类的对象。dynamic_cast做的就是后一件事。
2)以一个对象的确切类型作为过渡,得到描述该类型其他性质的信息。

如果typeid()是一个函数,那么它的声明大概是这个样子的:
class type_info;
const type_info& typeid(type_name);    //pseudo declaration
const type_info& typeid(expression);    //pseudo declaration
也就是说,typeid()返回某个未知类型的引用。给它一个type_name(类型名)作为参数,typeid()返回到一个type_info的引用,该type_info表示这个type_name。给定一个expression(表达式)作为参数,typeid()返回到一个type_info引用,这个type_info表示expression所指向的类型。

type_info类type_info类在标准头文件<type_info.h>定义,如果想使用typeid()的结果,就需要包含这个头文件。type_info类的确切定义是与实现有关的,但是它应该是一个多态类型,提供比较和另外一个操作,该操作返回所表示的类型名:
class type_info
{
    //implementation-dependent representation
private :
    type_info(const type_info &)    //users connot
    type_info &operator = (const type_info &);    //copy type_info
public :
    virtual ~type_info();                                                //is polyorphic

    int operator == (const type_info &) const;            //can be compared
    int operator !=  (const type_info &) const;
    int before (const type_info &) const;                     //ordering

    const char *name() const;                                       //name of type
};
函数before()是为了使type_info信息能够排序,以便能够通过散列表的方式访问它们。由befor()定义的顺序和继承关系之间没有任何关系。进一步说,就是对不同程序或者同一个程序的不同运行,我们都不能保证before()能产生同样的结果。在这个方面,befroe()和取地址符类似。

我们无法定义或拷贝type_info类型的对象,也不能为type_info对象赋值。创建type_info对象的唯一途径就是使用typeid运算符。

继续说回typeid运算符。typeid运算符可以作用与任何表达式。如果表达式是一个引用,则typeid返回该引用类型。不过当typeid作用于函数或数组时,并不会执行指向指针的标准类型的转换。也就是是说,如果我们队数组a执行typeid(a),则所获得的结果是数组类型而非指针类型。比如:
 int a[10];
 std::cout << typeid(a).name() << std::endl;

显示的结果是 int  [10],说明a是一个长度为10 的整型数组。而不是返回一个int *;这也说明了在C/C++里面,数组名和指针并不完全相等。

当运算对象不属于类类型(比如内置类型)或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型。而当运算对象是定义了至少一个虚函数的类的左值时,typeid的结果知道运行时才会求得。

typeid的使用例子:
#include <iostream>class Dialog{public : virtual void print() = 0;};class Dialog_box : public Dialog{public : virtual void print() {  std::cout << "this is class Dialog_box" << std::endl; } virtual int ask() {  return 0; }};class DBox_w_str : public Dialog_box{public: virtual void print() {  std::cout << "this is class DBox_w_str" << std::endl; } int ask() {  return 0; } virtual char *get_string() {  return nullptr; }};void My_func(DBox_w_str *bp1, Dialog_box *bp2){ if(typeid(bp1) == typeid(bp2)) {  bp1->print(); } else {  std::cout << "both is not equal!" << std::endl; }}int main(){ DBox_w_str *dbp = new DBox_w_str; Dialog_box *bp = new Dialog_box; My_func(dbp, bp); system("PAUSE"); return 0;}<span style="background-color: rgb(255, 255, 255); line-height: 1.5;"></span>

当typeid作用于指针(而不是指针所指的对象的时候),返回的结果是该指针的静态编译时的类型。当且仅当类含有虚函数时,typeid的表达式才会被求值,否则typeid返回表示的静态类型,编译器无需对表示求值也能知道表达式的静态类型。
修改上面的例子:
#include <iostream>class Dialog{public : virtual void print() = 0;};class Dialog_box : public Dialog{public : virtual void print() {  std::cout << "this is class Dialog_box" << std::endl; } virtual int ask() {  return 0; }};class DBox_w_str : public Dialog_box{public: virtual void print() {  std::cout << "this is class DBox_w_str" << std::endl; } int ask() {  return 0; } virtual char *get_string() {  return nullptr; }};void My_func(DBox_w_str *bp1, Dialog_box *bp2){ if(typeid(*bp1) == typeid(*bp2)) {  bp1->print();    // it works } else {  std::cout << "both is not equal!" << std::endl; }}int main(){ DBox_w_str *dbp = new DBox_w_str; Dialog_box *bp = new DBox_w_str;//这两个指针都指向DBox_w_str类型的对象。 My_func(dbp, bp); system("PAUSE"); return 0;}
如果这个时候,dbp或者bp是一个空指针的话,typeid就会抛出一个名为bad_typeid的异常。


0 0
原创粉丝点击