C++中的强制类型转换

C++中的强制类型转换(转贴)

                                      

 

这几天修改系统的bug,每天都会遇到一些很“有趣”的事情,写出来和大家分享。该系统的寿命也不算短,有五、六年了。开发人员换了多代,问题层出不穷。

没有自动化测试来保障的系统就是这样,改正一个bug可能引入更多其它的 bug,错误率一直具高不下;开发人员也变的“畏手畏脚”的,不敢对系统进行大胆地重构,代码结构就越来越乱,要想添加或者修改一个特性也就越来越困难。整个一“恶性循环”,真是苦不堪言。

1. 真是奇怪,到底哪儿出了问题

       上午刚上班,兴冲冲地打开测试数据库,先给未改正的bug排一个优先级。排名第一的是一个严重的“指针错误”:从窗口上删除一个组合后的图标会产生指针错误,提示访问非法内存地址空间。

       根据经验,修改这类错误最有效的方法是进行调试跟踪。通过调试,终于找到了出错的代码。

     …

DelIconInfo(((CIcon*)Icon)->ansIconName);

错误是找到了,but why?

偶以为是由于Icon是空指针造成的,可查看一下,它不是空指针,再看一下ansIconName它是一个空字符串,但这不会造成非法访问内存空间的错误啊?汗啊!

2. 柳暗花明

       记得初中的时候看过陈青云的一本小说,主人公是一位出色的剑客。他的一句话让我印象特深刻:越是困难和危险的时候越要保持头脑冷静。

       仔细查看了一下代码,终于发现了问题所在。上面程序中Icon取自一个链表,而链表添加的时候是CGraphElement类型的。代码大致如下:

    //

//往链表里添加元素 位于函数f1

       CgraphElement *pElement;

    ….

pList->Add(pElement);

//对链表进行处理 位于函数f2

for(each Icon in pList){

    DelIconInfo(((CIcon*)Icon)->ansIconName);

}

呵呵,添加和处理的指针类型是不一致的,又采用了“暴力”的强制类型转换,不出问题才怪那。欣喜之下,偶仔细查看了一下CgraphElement和Cicon的定义。

class CGraphElement: public Cobject{

         …

};

class CIcon: public Cobject{

     …

         String ansIconName;

};

CgraphElement对象本来没有ansIconName这个属性,把它强制类型转换后,虽然编译通过,但((CIcon*)Icon)->ansIconName指向的实际上是一个不属于自己的地址空间,难怪会报“非法访问地址空间”的错误了。

3. 亡羊补牢,为时为晚

       去年实习面试时,Boss对偶说:犯错误并不可能怕,关键要知错能改。

      

       其实,关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。下面对它们一一进行介绍。

3.1 static_cast

      

         用法:static_cast < type-id > ( expression )

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

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

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

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

l         把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

3.2 dynamic_cast

              用法:dynamic_cast < type-id > ( expression )

       该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

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

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

       class B{

public:

       int m_iNum;

       virtual void foo();

};

class D:public B{

    public:

       char *m_szName[100];

};

void func(B *pb){

    D *pd1 = static_cast<D *>(pb);

    D *pd2 = dynamic_cast<D *>(pb);

}

在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。

class A{

public:

        int m_iNum;

      virtual void f(){}

};

class B:public A{

};

class D:public A{

};

void foo(){

       B *pb = new B;

    pb->m_iNum = 100;

    D *pd1 = static_cast<D *>(pb);    //copile error

    D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL

delete pb;

}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。

3.3 reinpreter_cast

       用法:reinpreter_cast<type-id> (expression)

type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。

3.4 const_cast

       用法:const_cast<type_id> (expression)

       该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

Voiatile和const类试。举如下一例:

class B{

       public:

        int m_iNum;

}

void foo(){

const B b1;

b1.m_iNum = 100;            //comile error

B b2 = const_cast<B>(b1);

b2. m_iNum = 200;           //fine
    }

   

    上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值