C++ 类型转换及RTTI


一、C++的4中类型转换
我们应该比较熟悉C的类型转换即由圆括号和标识符组成,但是对于C的类型转换有时候到不到我们的要求,比如去除const 的类型转换,把一个指向基类的指针转化成指向子类的指针等等。下面就介绍C++引进的四个新的类型转换操作符,这四个操作符是:static_cast, const_cast, dynamic_cast, 和reinterpret_cast。

1,const_cast 除去对象的常属性。转换的是表达式而非自身. 形式:const_cast< type > ( object )

2,static_cast 用来进行非多态的任何转换。拒绝了运行时的类型检查。形式:static_cast< type > ( object )

3,dynamic_cast 这是唯一的运行时转换符。可完成类族中的向下类型转换——将父类的指针变为子类的指针。形式:dynamic_cast< type > (object)

4,reinterpret_cast 将一种数据从根本上变为另一种完全不兼容的类型。形式:reinterpret_cast< type > ( object )

下面分别仔细介绍:

const_cast< type > ( object )

该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
A、常量指针被转化成非常量的指针,并且仍然指向原来的对象;
B、常量引用被转换成非常量的引用,并且仍然指向原来的对象;

下面是一些例子:

class Test
{
public:
    Test(int m_=0):m(m_){}
public:
    int m;
};
int main()
{
    const Test t1;
    //b1.m = 1; //compile error
    // 体现出转换为指针类型
    Test *t2 = const_cast<Test*>(&t1);
    //左侧为引用类型
    Test &t3 = const_cast<Test&>(t1);
    //对t2或t3的数据成员做改变,就是对t1的值在做改变
    t2->m = 2;    //ok
    printf("%d\n", t1.m);
    t3.m = 3;    //ok
    printf("%d\n", t1.m);
    return 0;
}

使用const_cast可以返回一个指向非常量的指针(或引用)指向原const 常量对象,可以通过转换后的指针(或引用)对它的成员进行改变。

 

可见只有使用const_cast才能将 const性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。除此之外,

用 const_cast 符来执行其他任何类型转换,都会引起编译错误。

static_cast< type > ( object )

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
    进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

③把空指针转换成目标类型的空指针。

④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

编译器隐式执行的任何类型转换都可以由static_cast显式完成。如:

float pi=3.1415;
int a=static_cast<int>(pi);

dynamic_cast< type > (object)

该运算符把object转换成type类型的对象。type必须是类的指针、类的引用或者void*;

dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(基类指针或者引用指向一个派生类对象,把基类指针或引用转换为派生类指针或引用)

这个运算符会传回适当转型过的指针。如果downcast不安全(基类指针或者引用没有指向一个派生类对象),这个运算符会传回空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

例子:

class Base
{
public:
    virtual void print()
    {
        printf("I am Base. virtual print.\n");
    }
};

class Der:public Base
{
public:
    void print()
    {
        printf("I am Derive. virtual print.\n");
    }
    void print_non_vir()
    {
        printf("I am Derive. non virtual print.\n");
    }
};
int main()
{
    Base b;
    Der d;
    Base *pbb=&b;
    Base *pbd=&d;
    pbd->print();
//    pbd->print_non_vir(); //error
    dynamic_cast<Der*>(pbd)->print_non_vir();
 //  dynamic_cast<Der*>(pbb)->n=2;//error pbb所指对象为Base
    static_cast<Der*>(pbd)->print_non_vir();
    return 0;
}

注意:Base要有虚函数,否则会编译出错;static_cast则没有这个限制。

dynamic_cast转换符只能用于指针或者引用。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast转换操作符在执行类型转换时首先将检查能否成功转换,

如果能成功转换则转换之,如果转换失败,如果是指针则反回一个0值,如果是转换的是引用,则抛出一个bad_cast异常,所以在使用dynamic_cast转换之间应使用

if语句对其转换成功与否进行测试,比如

if(dynamic_cast<D*>(pb))
{
    ...
}
else
{
    ...
}

dynamic_cast能将基类类型的指针或引用安全地转换为派生类型的指针或引用, 它是怎么实现的呢?

它是通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型,在第2部分会介绍。

reinterpret_cast< type > ( object )

type 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针

(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。

C++中static_cast和reinterpret_cast的区别:

C++primer里写了编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释。

C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。比如:

int i;
char *p = "This is a test.";
i = reinterpret_cast<int>(p);

此时结果,i与p的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i.

二、RTTI
RTTI(Run-Time Type Information,通过运行时类型信息)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。


RTTI提供了以下两个非常有用的操作符:
(1)typeid操作符,返回指针和引用所指的实际类型。
(2)dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

typeid 操作符的结果是名为type_info的标准库类型的对象引用,类type_info在库头文件 typeinfo中定义。typeid 操作符可以与任何类型的表达式一起使用。

内置类型的表达式以及常量都可以用作 typeid 操作符的操作数。如果操作数不是类类型或者是没有虚函数的类,则 typeid 操作符指出操作数的静态类型;

如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型。

Base *bp;

    Derived *dp;

    // compare type at run time of twoobjects

    if (typeid(*bp) ==typeid(*dp)) {

        // bp and dp point to objects of thesame type

    }

    // test whether run time type is aspecific type

    if (typeid(*bp) ==typeid(Derived)) {

        // bp actually points to aDerived

    }//特别注意,只有当 typeid 的操作数是带虚函数的类类型的对象的时候,才返回动态类型信息.

type_info的复制构造函数以及赋值操作符都定义为 private,所以不能定义或复制 type_info类型的对象。程序中创建 type_info 对象的唯一方法是使用 typeid 操作符。

type_info类确切定义随编译器而变化,但是标准保证至少实现operator==,operator=!,type_info::name()及type_info::before()。
运行时获知变量类型名称,可以使用 typeid(变量).name ,如:printf("%s\n", typeid(int).name()); 其输出不一定是 int ,我这里输出是 i。

对dynamic_cast 它究竟是怎么实现的呢?

最简单的方案就是将信息保存在vtable里,它会占用一个vtalbe表的项目,具体做法可以参考大作《Inside C++ Model》RTTI 的info 是如何和对象之间的关联。

举例

class Point
{
public:
  Point( float xval );
  virtual ~Point();
  float x() const;
  static int PointCount();
protected:
  virtual ostream& print( ostream &os ) const;
  float _x;
  static int _point_count;

};

其对应的内存模型:

通过上图可知 RTTI  info 存在于虚表的第一项。可以回答 “dynamic_cast转换符只能用于含有虚函数的类”,因为RTTI 依赖于虚表,所以用dynamic_cast 对应的类一定要有虚函数。

本文永久更新链接地址

相关内容