c++之运行时类型识别

本文详细介绍了C++中的运行时类型识别(RTTI)和强制转换运算符,包括RTTI的使用如typeid和type_info,以及四种新的强制转换运算符:dynamic_cast、const_cast、reinterpret_cast和static_cast。RTTI允许在运行时识别对象的类型,dynamic_cast用于多态类型的安全转换,其他转换运算符则提供了不同场景下的类型转换功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 这一部分将讨论C++中两个支持”现代 面向对象程序设计“的特征。运行时类型识别(Run-Time Type Identification,RTTI)和强制转换运算符。C++的初始定义并没有包含这两个特征,增加他们是为了增强C++对运行时多态的支持。RTTI允许应用程序在执行期间标识一个对象的类型。而强制转换运算符可以给你带来更安全的、更加可控的类型转换方法。你将会看到强制转换运算符之一dynamic_cast是与RTTI直接相关的。所以将”强制转换运算符“和RTTI放在一起讨论。

      对你来说,运行时的类型识别可能是个新东西,因为在非多态语言(例如:C)中找不到这个概念的。非多态语言不需要运行时的类型信息,因为每个对象的类型在编译时就已经确定了(例如:在写程序的时候我们指定了对象的类型)。但是在支持多态的语言中(例如C++),可能存在这种情况:在编译时你并不知道某个对象的类型信息,而只有在程序运行时才能获得对象的准确信息。你已经知道,C++是通过类的层次结构、虚函数以及基类指针来实现多态的。基类指针可以用来指向基类的对象或者其派生类的对象,也就是说,我们并不总是能够在任何时刻都预先知道基类指针所指向对象的实际类型。因此,必须在程序中使用“运行时类型识别”来识别对象的实际类型。

      我们可以通过typeid来获得一个对象的类型,在使用typeid之前,你必须在程序中包含头文件<typeinfo.h>。typeid的最常见使用形式如下:typeid(object).其中,object是你想获得“类型信息”的对象。这个对象可以是任意的类型,包括C++的内置类型以及你创建的类型,typeid将返回一个type_info 类型的对象引用 来描述object的类型信息。在type_info中定义了下面这些公有成员:

bool operator==(const type_info &ob);

bool operator!=(const type_info &ob);

bool before(const type_info &ob);

const char* name();

其中重载的运算符==和!=可以用来比较类型信息,如果在类型信息的列表中"调用before的对象类型"在"对象ob的类型"之前,那么函数before将返回 真,否则返回假(这个函数通常在内部使用,函数的返回值与“继承”或者“类的层次结构”是没有关系的)。函数name()将返回一个指向类型名字的指针:下面是使用typeid的示例程序:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class myClass  
  4. {};  
  5. void main()    
  6. {    
  7.     cout<<"Start"<<endl;   
  8.     int i,j;  
  9.     float f;  
  10.     myClass ob;  
  11.     cout<<"The type of i is:"<<typeid(i).name()<<endl;  
  12.     cout<<"The type of f is:"<<typeid(f).name()<<endl;  
  13.     cout<<"The type of i ob:"<<typeid(ob).name()<<endl;  
  14.     if(typeid(i)==typeid(j))  
  15.         cout<<"The type i and j are the same"<<endl;  
  16.     if(typeid(i)!=typeid(f))  
  17.         cout<<"The type i and f are the same"<<endl;  
  18.     cout<<"End"<<endl;    
  19. }    
  20. //输出结果  
  21. Start  
  22. The type of i is:int  
  23. The type of f is:float  
  24. The type of i ob:class myClass  
  25. The type i and j are the same  
  26. The type i and f are the same  
  27. End  
 

我们可以将typeid应用于多态基类(多态类是包含虚函数的类)指针。这也可能是typeid最重要的用途。在这种情况中,typeid将自动的返回指针所指向对象的实际类型,返回值可能是基类对象或者从基类中派生类的对象().因此,通过使用typeid你可以在运行时确定基类指针指向对象的实际类型。下面的程序说明了这种用法:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class base  
  4. {  
  5.     virtual void f(){}//基类是多态类  
  6. };  
  7. class Derived1 : public base  
  8. {};  
  9. class Derived2 : public base  
  10. {};  
  11. void main()    
  12. {    
  13.     cout<<"Start"<<endl;   
  14.       
  15.     base *p,baseob;  
  16.     Derived1 ob1;  
  17.     Derived2 ob2;  
  18.     p=&baseob;  
  19.     cout<<"p is pointing to object of type is:"<<typeid(*p).name()<<endl;  
  20.     p=&ob1;  
  21.     cout<<"p is pointing to object of type is:"<<typeid(*p).name()<<endl;  
  22.     p=&ob2;  
  23.     cout<<"p is pointing to object of type is:"<<typeid(*p).name()<<endl;  
  24.     cout<<"End"<<endl;    
  25. }    
  26. //输出结果  
  27. p is pointing to object of type is:base  
  28. p is pointing to object of type is:Derived1  
  29. p is pointing to object of type is:Derived2  
 

在程序中,当我们对“多态类型的基类指针”使用typeid时,就可以在运行时确定指针指向对象的实际类型,并输出这个类型的名字。如果typeid被应用于非多态指针类,那么我们得到的将是指针被声明的类型。也就是说,此时typeid并不会返回指针所指向对象的实际类型。我们可以验证这种情况,将虚函数f()从类base中注释掉并观察修改后程序的输出结果,你将看到,程序输出的每个对象的类型将变成base,因为指针p的类型是base。

      多态类对象“引用”的用法类似于对象指针,当typeid应用于“多态类对象的引用"时,他将返回引用指向对象的实际类型,可能是基类,也可能是派生类型。当对象引用被作为参数传递给函数时,我们需要经常使用typeid来获得对象的实际类型。例如:下面程序中的函数WhatType()声明了一个base类型的引用参数。这意味着可以将”base类或者base类的派生类“的对象引用作为参数传递给函数。当对这个参数使用typeid是,我们就可以得到传递给函数的对象的实际类型。

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class base  
  4. {  
  5.     virtual void f(){}//基类是多态类  
  6. };  
  7. class Derived1 : public base  
  8. {};  
  9. class Derived2 : public base  
  10. {};  
  11. void WhatType(base &ob)  
  12. {  
  13.     cout<<"ob is referercing an object of type:"<<typeid(ob).name()<<endl;  
  14. }  
  15. void main()    
  16. {    
  17.     cout<<"Start"<<endl;   
  18.       
  19.     int i;  
  20.     base baseob;  
  21.     Derived1 ob1;  
  22.     Derived2 ob2;  
  23.     WhatType(baseob);  
  24.     WhatType(ob1);  
  25.     WhatType(ob2);  
  26.       
  27.     cout<<"End"<<endl;    
  28. }    
  29. //输出结果  
  30. ob is pointing to object of type is:base  
  31. ob is pointing to object of type is:Derived1  
  32. ob is pointing to object of type is:Derived2  
 

typeid还有第二种使用形式,将一个类型名字作为参数,如下所示:

typeid(type_name);

例如:下面的语句时完全正确的:cout<<typeid(int).name();

使用这种形式的typeid的主要目的在于,获得一个描述特定类型的type_info对象,这个对象可以被用在类型比较语句中。

强制转换运算符

      C++共定义了5种“强制转换运算符”。第一种是传统的强制转换,它是C++初始定义的一部分,其余4种强制转换运算符后来才被C++定义中,它们分别是dynamic_cast、const_cast、reinterpret_cast、static_cast。我们可以使用这些运算符来增强对强制转换的控制。下面分别学习每种强制转换运算符。

1.1dynamic_cast---对 多态类型 进行 运行时 的 强制转换

      dynamic_cast可能是所有新增“强制转换运算符”中最重要的一个。dynamic_cast执行的是运行时强制转换。这样就可以确保强制转换的合法性。如果在执行dynamic_cast时的强制转换是非法的,那么强制转换将会失败。dynamic_cast的通用形式如下:

dynamic_cast<target-type> (expr);其中,target-type指定了强制转换的目标类型。expr是需要进行强制转换的表达式。目标类型必须是“指针类型”或者“引用类型”。而需要进行强制转换的表达式必须能够被求值为“指针类型”或者“引用类型”的结果。也就是说,我们可以使用dynamic_cast将指针或者引用从一种类型转换为另一种类型。

      dynamic_cast主要被用来执行 多态类型之间的强制转换。例如假设2个多态类B和D,其中D是派生于B的。这样我们就可以使用dynamic_cast将指针从类型D*转换为类型B*。理由是基类型的指针通常可以指向派生类的对象。然而,只有当指针指向的对象“确实是”类D的对象时,dynamic_cast才能将指针从类型D*转换为类型B*。通常,只有“需要被强制转换的指针(或引用)”指向的对象是目标类型 或者目标类型的派生类型时,强制转换运算符才会成功;否则,强制转换运算符失败。如果在转换指针的类型时失败,那么经过dynamic_cast运算后得到的指针将会使空值。如果在转换引用的类型时失败,那么dynamic_cast将会抛出bad_cast类型的异常。

      下面是个简单的示例程序,其中假设Base是多态类,并且Derived派生与Base。

 

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base  
  4. {  
  5.     virtual void f(){}//基类是多态类  
  6. };  
  7. class Derived : public Base  
  8. {};  
  9. void main()    
  10. {    
  11.     cout<<"Start"<<endl;   
  12.       
  13.     Base *bp,b_ob;  
  14.     Derived *dp,d_ob;  
  15.     bp=&d_ob;//基类指针指向派生类型的对象  
  16.     dp=dynamic_cast<Derived* >(bp);//将指向派生类的基类指针 强制转换为派生类型的指针,真确。  
  17.     if(dp) cout<<"Cast OK"<<endl;  
  18.       
  19.     cout<<"End"<<endl;    
  20. }    
  21. //运行结果:  
  22. Start  
  23. Cast OK  
  24. End  
 

上面的代码中,将基类型的指针bp强制转换为派生类型的指针dp是可行的,因为指针bp指向的对象确实是一个Dereived类型的对象。因此,程序将会输出Cast Ok。但在在下面的示例中,强制转换时失败的,因为bp指向的对象是一个Base类型的对象,此时,将基类型的指针强制转换为派生类型的指针是非法的。除非指针指向的对象时派生类型的对象。

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base  
  4. {  
  5.     virtual void f(){}//基类是多态类  
  6. };  
  7. class Derived : public Base  
  8. {};  
  9. void main()    
  10. {    
  11.     cout<<"Start"<<endl;   
  12.       
  13.     Base *bp,b_ob;  
  14.     Derived *dp,d_ob;  
  15.     bp=&b_ob;//基类指针指向派生类型的对象  
  16.     dp=dynamic_cast<Derived* >(bp);//将指向派生类的基类指针 强制转换为派生类型的指针,真确。  
  17.     if(dp)   
  18.         cout<<"Cast OK."<<endl;  
  19.     else  
  20.         cout<<"Cast Fails."<<endl;  
  21.       
  22.     cout<<"End"<<endl;    
  23. }    
  24. //运行结果:  
  25. Start  
  26. Cast Fails.  
  27. End  
 

下面的程序说明了dynamic_cast的多种情况:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base//基类是多态类  
  4. {  
  5. public:  
  6.     virtual void f()  
  7.     {  
  8.         cout<<"Inside Base."<<endl;  
  9.     }  
  10. };  
  11. class Derived : public Base  
  12. {  
  13. public:  
  14.     void f()  
  15.     {  
  16.         cout<<"Inside Derived."<<endl;  
  17.     }  
  18. };  
  19. void main()    
  20. {    
  21.     cout<<"Start"<<endl;   
  22.       
  23.     Base *bp,b_ob;  
  24.     Derived *dp,d_ob;  
  25.     //-------------------------------------  
  26.     dp=dynamic_cast<Derived*>(&d_ob);//派生类对象的指针“强制转换为”派生类对象的指针 OK  
  27.     if(dp)  
  28.     {  
  29.         cout<<"Cast from Derived* to Derived* Ok."<<endl;  
  30.         dp->f();  
  31.     }  
  32.     else  
  33.         cout<<"Error!"<<endl;  
  34.     //--------------------------------------  
  35.     bp=dynamic_cast<Base*>(&d_ob);//派生类对象的指针“强制转换为”基类对象的指针 OK  
  36.     if(bp)  
  37.     {  
  38.         cout<<"Cast from Derived* to Base* OK."<<endl;  
  39.         bp->f();  
  40.     }  
  41.     else  
  42.         cout<<"Error!"<<endl;  
  43.     //---------------------------------  
  44.     bp=dynamic_cast<Base*>(&b_ob);//基类对象的指针“强制转换为”基类对象的指针 OK  
  45.     if(bp)  
  46.     {  
  47.         cout<<"Cast from Base* to Base* OK."<<endl;  
  48.         bp->f();  
  49.     }  
  50.     else  
  51.         cout<<"Error!"<<endl;  
  52.     //-------------------------------------  
  53.     bp=dynamic_cast<Derived*>(&b_ob);//基类对象的指针“强制转换为”派生类对象的指针 Error  
  54.     if(bp)  
  55.     {  
  56.         cout<<"Cast from Base* to Base* OK."<<endl;  
  57.         bp->f();  
  58.     }  
  59.     else  
  60.         cout<<"Cast from Base* to Base* not OK."<<endl;  
  61.     //=====================================================  
  62.     bp=&d_ob;  
  63.     dp=dynamic_cast<Derived*>(bp);//指向派生类的基类指针“强制转换为”派生类对象的指针 OK  
  64.     if(dp)  
  65.     {  
  66.         cout<<"casting bp to a Drived* OK."<<endl;  
  67.         cout<<"because bp is really pointing to a Derived object."<<endl;  
  68.         dp->f();  
  69.     }  
  70.     else   
  71.         cout<<"Error!"<<endl;  
  72.     //-------------------------------------------------------  
  73.     bp=&b_ob;//bp指向基类型的对象  
  74.     dp=dynamic_cast<Derived*>(bp);//指向基类型对象的指针“强制转换为”派生类对象的指针 OK  
  75.     if(dp)  
  76.         cout<<"Error!"<<endl;  
  77.     else  
  78.     {  
  79.         cout<<"Now casting bp to a Drived* is not ok."<<endl;  
  80.         cout<<"because bp is really pointing to a Base object."<<endl;  
  81.     }  
  82.     //---------------------------------------------------------  
  83.     dp=&d_ob;//dp指向基类型的对象  
  84.     bp=dynamic_cast<Base*>(dp);//指向派生类型对象的指针“强制转换为”指向基类对象的指针 OK  
  85.     if(dp)  
  86.     {  
  87.         cout<<"Casting dp to a base* is ok"<<endl;  
  88.         bp->f();  
  89.     }  
  90.     else  
  91.         cout<<"Error"<<endl;  
  92.     cout<<"End"<<endl;    
  93. }    
  94. //运行结果:  
  95. Start  
  96. Cast from Derived* to Derived* Ok.  
  97. Inside Derived.  
  98. Cast from Derived* to Base* OK.  
  99. Inside Derived.  
  100. Cast from Base* to Base* OK.  
  101. Inside Base.  
  102. Cast from Base* to Base* not OK.  
  103. casting bp to a Drived* OK.  
  104. because bp is really pointing to a Derived object.  
  105. Inside Derived.  
  106. Now casting bp to a Drived* is not ok.  
  107. because bp is really pointing to a Base object.  
  108. Casting dp to a base* is ok  
  109. Inside Derived.  
  110. End  
 

在某些特定的情况下,运算符dynamic_cast可以用来代替typeid。例如:假设base是derived的多态基类,那么在下面的代码中,只有当指针bp指向的对象确实是derived类型的对象时,才把指针bp指向对象的地址赋给指针dp:

base* bp;

derived *dp;

//......

if( typeid(*bp) == typeid(*dp) ) dp=(derived*)bp;

上面的代码使用了传统形式的强制转换来转换指针的类型。这个转换是安全的,因为if语句在类型转换之前使用了typeid来检查转换的合法性。但是我们可以用更好的方法来代替上面代码实现的功能,那就是用运算符dynamic_cast来代替typeid和if语句:

dp=dynamic_cast<derived*>(bp);

因为只有“需要转换类型的指针”所指向的“对象”是“目标类型的对象”或者是“从目标类型派生的类型的对象”,dynamic_cast才会成功。所以在执行强制转换语句之后,指针dp将或者是一个空值或者是一个指向derived类型对象的指针。由于dynamic_cast只有在转换合法时才能成功。因此它能够简化在特定情况中的逻辑判断。下面的程序演示了如何使用dynamic_cast来代替typeid.程序执行2组相同的操作:首先使用typeid,然后使用dynamic_cast.

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base//基类是多态类  
  4. {  
  5. public:  
  6.     virtual void f()  
  7.     {  
  8.     }  
  9. };  
  10. class Derived : public Base  
  11. {  
  12. public:  
  13.     void derivedOnly()  
  14.     {  
  15.         cout<<"Is a derived object."<<endl;  
  16.     }  
  17. };  
  18. void main()    
  19. {    
  20.     cout<<"Start"<<endl;   
  21.       
  22.     Base *bp,b_ob;  
  23.     Derived *dp,d_ob;  
  24.     //******************************  
  25.     //使用typeid  
  26.     //******************************  
  27.     bp=&b_ob;  
  28.     iftypeid(*bp) == typeid(Derived) )  
  29.     {  
  30.         dp=(Derived*)bp;  
  31.         dp->derivedOnly();  
  32.     }  
  33.     else  
  34.         cout<<"Cast from base to derived failed."<<endl;  
  35.     //--------------------------------------------------  
  36.     bp=&d_ob;  
  37.     iftypeid(*bp) == typeid(Derived) )  
  38.     {  
  39.         dp=(Derived*)bp;  
  40.         dp->derivedOnly();  
  41.     }  
  42.     else  
  43.         cout<<"Error,cast should work!"<<endl;  
  44.     //******************************  
  45.     //使用dynamic_cast  
  46.     //******************************  
  47.     bp=&b_ob;  
  48.     dp=dynamic_cast<Derived*>(bp);  
  49.     if(dp)  
  50.         dp->derivedOnly();  
  51.     else  
  52.         cout<<"Cast from base to derived failed."<<endl;  
  53.     //---------------------------------------------------  
  54.     bp=&d_ob;  
  55.     dp=dynamic_cast<Derived*>(bp);  
  56.     if(dp)  
  57.         dp->derivedOnly();  
  58.     else  
  59.         cout<<"Error,cast should work!"<<endl;  
  60.     cout<<"End"<<endl;    
  61. }    
  62. //运行结果:  
  63. Start  
  64. Cast from base to derived failed.  
  65. Is a derived object.  
  66. Cast from base to derived failed.  
  67. Is a derived object.  
  68. End  
 

从程序中可以看到,dynamic_cast简化了从基类指针转化为派生类指针时所需要的逻辑判断。最后一点:强制转换运算符dynamic_cast也可以用在模板类中。

const_cast

const_cast 去掉对象的const和/或volatile属性。在强制转换中,const_cast被用来显式的去掉变量的const或者volatile属性。在这种强制转换运算中,目标类型与源类型必须是一致的。只是源类型中的const或者volatile属性在目标类型中被去掉了。我们经常使用const_cast 去掉const属性。const_cast的通用形式如下:

const_cast<type>(expr);其中type指定了强制转换的目标类型,expr是需要进行强制转换的表达式。下面的程序说明了const_cast的用法:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. //#include <typeinfo.h>  
  3. void f(const int *p)  
  4. {  
  5.     int *v;  
  6.     //去掉const属性  
  7.     v=const_cast<int*>(p);  
  8.     *v=100;  
  9. }  
  10. void main()    
  11. {    
  12.     cout<<"Start"<<endl;   
  13.       
  14.     int x=99;  
  15.     cout<<"x before call:"<<x<<endl;  
  16.     f(&x);  
  17.     cout<<"x after call:"<<x<<endl;  
  18.       
  19.     cout<<"End"<<endl;    
  20. }    
  21. //运行结果:  
  22. Start  
  23. x before call:99  
  24. x after call:100  
  25. End  
 

从程序中可以看到,虽然在函数f中参数被指定为一个const指针,但仍然可以修改变量x的值。需要强调的是:使用const_cast来去掉const属性会带来潜在的风险,所以应该谨慎的使用这个运算符。另外一点,只有const_cast才能去掉const属性,也就是说,无论是dynamic_cast、reinterpret_cast还是static_cast都不能改变一个对象的const属性。

static_cast

运算符static_cast执行的是非多态强制转换。它可以被用在所有标准的强制转换中,并且在执行强制转换时不会做任何运行时的检测。static_cast的通用形式如下:

static_cast<type>(expr);其中,type指定了强制转换的目标类型,expr是需要进行强制转换的表达式。从本质上来说,运算符static_cast就是用来替代传统形式的强制转换运算符,它执行的仅仅是一个非多态的强制转换。例如:下面的代码将float类型转换为int类型:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. //#include <typeinfo.h>  
  3. void main()    
  4. {    
  5.     cout<<"Start"<<endl;   
  6.       
  7.     int i;  
  8.     float f=199.22f;  
  9.     i=static_cast<int>(f);  
  10.     cout<<i<<endl;  
  11.     cout<<"End"<<endl;    
  12. }    
  13. //运行结果:  
  14. Start  
  15. 199  
  16. End  
 

reinterpret_cast

reinterpret_cast可以将一种类型强制转换为另一种不同的类型。例如:它可以将一个指针转化为整数,或者将一个整数转换为指针。它也可以用来转换不相容的指针类型。reinterpret_cast的通用形式如下:

reinterpret_cast<type>(expr);其中,type指定了强制转换的目标类型,expr是需要进行强制转换的表达式。下面的程序说明了reinterpret_cast的用法:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. //#include <typeinfo.h>  
  3. void main()    
  4. {    
  5.     cout<<"Start"<<endl;   
  6.       
  7.     int i;  
  8.     char* p="This is a string";  
  9.     i=reinterpret_cast<int>(p);  
  10.     cout<<i<<endl;  
  11.     cout<<"End"<<endl;    
  12. }    
  13. //运行结果:  
  14. Start  
  15. 4378964  
  16. End  
 

在上面的程序中,强制转换运算符reinterpret_cast将指针p强制转换为一个整数。这种转换代表最基本的类型变换,很好的说明了reinterpret_cast的用途。

深入

传统的强制转换与4种新的强制转换

你可能想使用本章描述的强制转换运算符来完全代替传统形式的强制转换。这种想法通常会带来一个问题:“我应该使用传统形式的强制转换运算符还是这4种新的强制转换运算符呢”。对于这个问题到目前为止还没有一个所有程序员都认同的准则。因为,在从一种类型数据强制转换为另一种类型数据时,新的强制转换运算符能够降低类型转换中内在的危险性,因此许多C++程序员觉得应该使用新的强制转换运算符。当然,这样做是完全正确的。然而,其它的程序员则认为传统形式的强制转换已经被使用很多年,不应该轻易放弃。例如:有些人会认为在

简单的以及相对安全的强制转换中,传统形式的强制转换完全足够。

      然而对于一点,所有程序员都没有分歧;在执行多态类的强制转换时,应该明确使用dynamic_cast.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值