常见错误29: 以void*为类型转换的中介类型----读书笔记《c++ gotchas》...
时间:2010-08-09 来源:lzueclipse
使用类型转换时,void*是个次等选项,能不用就不用。
在强制类型转换中,转换到void*的后果是将带类型的指针(typed pointer)的类型信息系数抹除。、
void *vp = new int(12);
//…
int *ip = static_cast(vp);//能挣扎着运作一阵
记住,我们使用static_cast只能用来进行相对安全和可移植性的类型转换。比如,用static_cast把一个基类类型指针转化成
一个public派生于它的类型指针。
对于非安全的、平台相关的类型转换,我们除了使用reinterpret_cast之外别无选择。
char *cp = static_cast<char *> (ip);//错误
char *cp = reinterpret_cast<char *>(ip);//合法
使用reinterpret_cast给维护工程师一个明确信号,就是这段代码进行的强制转换未进行可移植性
方面的考虑。
若是以void*作为类型转换的中介类型:
char *cp = static_cast<char *>(vp);//把指向int的指针装换程指向char的指针
还有更早的。考虑某用户界面系统的实现,它允许把某种“窗口组建”(Widget)的地址先存储起来以备后用:
typedef void *Widget;
void set Widget(Widget);
Widget getWidget();
我靠,必须用脑子记住存储的Widget的真实类型,以便需要的时间恢复其类型。
//某个头文件里定义的接口
class Button {
//…
};
class MyButton : public Button {
//…
};
//另一个文件里的代码
MyButton *mb = new MyButton;
setWidget(mb);
//完全不相干的另一段代码
Button *b = static_cast<Button *> (getWidget());//也许可以工作
上面这段代码通常可以运作,尽管我们在提取(extract)Widget时损失了部分的性别信息。
在利用Widget作为中介类型来指向存储的对象时,本应该是指向MyButton类型的对象的,但是在提取时,
按照了Button类型来提取。能够工作的原因是,和class对象的可能内存(注意是:可能)布局有关:
典型情况下,派生类对象把其基类子对象存储放在其偏移量为0的位置,如图所示。
所以,派生类对象的地址通常等同于其基类子对象的地址。(C++标准只保证以void*为中介类型来存储
的地址在设置和提取时使用同一类型,才能获取正确的结果,上面这段代码即使在单继承条件下也有可能
会无法运作,到常见错误70会介绍)。
这段代码不堪一击,在维护过程中,一个小小的改动就会引入一个缺陷。具体来讲,一个直截了当的把单继承
改为多继承就能让这段代码彻底崩溃:
//某头文件里定义的接口
class Subject {
//…
};
class ObservedButton : public Subject, public Button {
//多继承
//…
};
//另一个文件里的代码
ObservedButton *ob = new ObservedButton;
setWidget(ob);
Button *badButton = static_cast<Button *> (getWidget());//完蛋
问题出在多继承条件下派生类的内存布局上。典型地,基类列表中的第一个基类型子对象会被放置于派生类的
偏移量0处、而第二个紧随其后,然后是派生类的自有成员,如图所示。
在多继承条件下,单一对象往往有多个合法地址。
通常情况下,这不是问题,因为编译器总是针对每种基类类型子对象在派生类中的偏移量了如指掌,在编译
期就能经过必要的调整计算出正确地址:
Button *bp = new ObservedButton;
ObservedButton *obp = static_cast<obversedButton *> (bp);
bp正确地指向了ObservedButton类型的对象中存储器Button基类子对象的地址,而不是它的起始
地址(即偏移量为0的地址)。当把指向Button类型的指针强制转换回ObservedButton类型的指针
时,编译器能够调整期地址,使它指向到其起始地址。因为编译器了解每个基类类型的子对象在其派生
类对象中的偏移量。
真相大白,使用setWidget函数时,抹除了所有的类型信息。当企图对getWidget的结果执行强制类型转换
时,编译器就没法知道怎么去调整地址了。结果一个指向Button类型的指针实际上指向到了一个Subject类型
的子对象了。
针对void*作为转换的中介类型,一定要小心。