看一下下面的类继承层次: 指向类型D的指针转换为B或C都正常,但如果从D转换到A将会怎么样来?这个结果导致转换的二义性错误;为了结果这个问题,你可以指向两次明确的转型,例如: void f() { D* pd = new D; A* pa = dynamic_cast<A*>(pd); //错误:二义性 B* pb = dynamic_cast<B*>(pd); //首先转换到 B A* pa2 = dynamic_cast<A*>(pb); // ok:明确的 } 在使用虚基类的时候就导致更复杂的模糊;看下面的类层次图: 在这个继承层次中,A是虚基类。假定一个类E的实例并且一个指向A子对象的指针,一次到B的dynamic_cast会由于不明确性导致失败,你必须首先转换到适当的层次,然后再向上转换到确定的层次,一直按照这种方式直到到达正确的B对象。 看下面的类层次图: 假定一个类型E的对象和一个指向D子对象的指针,从D子对象导航到左上A子对象,必须执行三个转换。从D到E的dynamic_cast的转换,然后一个从E到B的转换(可以是dynamic_cast或者隐式转换),最终是从B到A的转换,例如: void f(D* pd) { E* pe = dynamic_cast<E*>(pd);// 这里的 D 实际上是 E 类型的对象 B* pb = pe; // upcast, implicit conversion A* pa = pb; // upcast, implicit conversion } 交叉转换 dynamic_cast 操作能执行交叉转换,使用上面相同的类层次,从 B 子对象到 D 子对象转换是可能的,只要完整的对象是 E . 由于交叉转换,从 D 指针到左上角 A 子对象的指针是可行的;首先从 D 到 B 的交叉转换,然后隐式从 B 到 A 的转换。例如: void f(D* pd) { B* pb = dynamic_cast<B*>(pd); // cross cast A* pa = pb; // upcast, implicit conversion } 一个 NULL 指针值通过 dynamic_cast 转换到一个 NULL 指针。 当使用 dynamic_cast <type-id> (expression)时,如果 expression 不能安全的转换到 type-id ,运行时检查导致转型失败,例如: class A { … }; class B { … }; void f() { A* pa = new A; B* pb = dynamic_cast<B*>(pa); // fails, not safe; B not derived from A … } 转换失败的指针类型是 NULL 指针。失败的引用类型转换抛出 bad_cast_exception 异常;如果 expression 没有指向或引用一个有效的对象将抛出 __non_rtti_object 异常。 再看下面看一个交叉转换例子: struct B1{ virtual ~B1(){} }; struct B2{ virtual ~B2(){} }; struct D1 : B1, B2{}; int main() { D1 d; B1* pb1 = &d; B2* pb2 = dynamic_cast<B2*>(pb1);//L1 B2* pb22 = static_cast<B2*>(pb1); //L2 失败 return 0; } 上述定义中可以看到,B1和B2是不相关的类,从L1可以看到,dynamic_cast允许这种转换:只要B1存在多态方法。 L2将编译失败,static_cast并不允许两个完全不相干的类互相转换。 dynamic_cast的这种特性,在提取一个对象的某个接口的时候,非常有用,它很类似于实现了COM的QueryInterface的功能。 dynamic_cast的讨论: 在探究 dynamic_cast 的设计意图之前,值得留意的是很多 dynamic_cast 的实现都相当慢。 例如,至少有一种通用的实现部分地基于对类名字进行字符串比较。如果你在一个 位于四层深的单继承体系中的对象上执行 dynamic_cast,在这样一个实现下的每一个 dynamic_cast 都要付出相当于四次调用 strcmp 来比较类名字的成本。对于一个更深的或使用了多继承的继承体系,付出的代价会更加昂贵。 对 dynamic_cast 的需要通常发生在这种情况下:你要在一个你确信为派生类的对象上执行派生类的操作,但是你只能通过一个基类的指针或引用来操控这个对象。 有两个一般的方法可以避免这个问题: 第一个,使用存储着直接指向派生类对象的指针的容器,从而消除通过基类接口操控这个对象的需要。当然,这个方法不允许你在同一个容器中存储所有可能的基类的派生类的指针。为了与不同的窗口类型一起工作,你可能需要多个类型安全(type-safe)的容器。 一个候选方法可以让你通过一个基类的接口操控所有可能的 Window 派生类,就是在基类中提供一个让你做你想做的事情的虚函数。例如,尽管只有 SpecialWindows 能 blink,在基类中声明这个函数,并提供一个什么都不做的缺省实现或许是有意义的: 所以: ·避免强制转型的随时应用,特别是在性能敏感的代码中应用 dynamic_casts,如果一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。 ·如果必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。 ·尽量用 C++(www.cppentry.com) 风格的强制转型替换旧风格的强制转型。它们更容易被注意到,而且他们做的事情也更加明确。 III. reinpreter_cast 用法:reinpreter_cast<type-id> (expression) type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。 该运算符的用法比较多。 IV. 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是两个不同的对象。
|