网络延时测试-华为微信主题

dynamic_cast
2023年4月3日发(作者:window 7下载)

关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++之父的《C++的设计和演

化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换

符:static_cast,dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、

reinterpret_cast、和const_cast。下面对它们一一进行介绍。

错误!未找到引用源。.static_cast

用法:static_cast(expression)

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。

它主要有如下几种用法:

①用于类层次结构中基类和子类之间指针或引用的转换。

进行上行转换(把子类的指针或引用转换成基类表示)是安全的;

进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以

是不安全的。

②用于基本数据类型之间的转换。如把int转换成char,把int转换成enum。这种转换的安

全性也要开发人员来保证。

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

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

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

错误!未找到引用源。.dynamic_cast

主要用于执行“安全的向下转型(safedowncasting)”,也就是说,要确定一个对象是否是

一个继承体系中的一个特定类型。

用法:dynamic_cast(expression)

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者

void*;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个

引用,那么expression也必须是一个引用。

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

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换

时,dynamic_cast具有类型检查的功能,比static_cast更安全。

classB{

public:

intm_iNum;

virtualvoidfoo();

};

classD:publicB{

public:

char*m_szName[100];

};

voidfunc(B*pb){

D*pd1=static_cast(pb);

D*pd2=dynamic_cast(pb);

}

在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个

指针执行D类型的任何操作都是安全的;但是,如果pb指向的是一个B类型的对象,那么

pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),

而pd2将是一个空指针。另外要注意:B要有虚函数,否则会编译出错;static_cast则没有

这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表

(关于虚函数表的概念,详细可见)中,只有定义了虚函数的类才

有虚函数表,没有定义虚函数的类是没有虚函数表的。

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

classA{

public:

intm_iNum;

virtualvoidf(){}

};

classB:publicA{

};

classD:publicA{

};

voidfoo(){

B*pb=newB;

pb->m_iNum=100;

D*pd1=static_cast(pb);//copileerror

D*pd2=dynamic_cast(pb);//pd2isNULL

deletepb;

}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用

dynamic_cast的转换则是允许的,结果是空指针。

//////////////////////////////////////////////////////////////////////////////////////////////////////

向上转换,比较安全,不再举例。

向void*转换

如果type-id是一个void*,运行时检查将决定表达式的实际类型。结果是一个到

expression指向的完整对象。例如:

classA{...};

classB{...};

voidf()

{

A*pa=newA;

B*pb=newB;

void*pv=dynamic_cast(pa);

//pv指向一个A类型的对象

...

pv=dynamic_cast(pb);

//pv指向一个B类型的对象

}

向下转换

如果type-id不是void*,运行时检查指向expression的对象能否转换为指向type-id类

型的对象。

如果expression类型是type-id的基类,运行时检查是否expression实际是一个指向

type-id类型的完整对象,如果是,结果返回指向type-id类型的完整对象,否则返回NULL。

例如:

classB{...};

classD:publicB{...};

voidf()

{

B*pb=newD;//unclearbutok

B*pb2=newB;

//判断一个基类指针指向的对象是否是一个指定子类型

D*pd=dynamic_cast(pb);//ok:pb实际指向D,返回D*

D*pd2=dynamic_cast(pb2);//pb2实际指向B而不是D,转换失败,pd2是NULL

...

}

在多重继承的情况,可能导致二义性。

看一下下面的类继承层次:

指向类型D的指针转换为B或C都正常,但如果从D转换到A将会怎么样来?这个结果导

致转换的二义性错误;为了结果这个问题,你可以指向两次明确的转型,例如:

voidf()

{

D*pd=newD;

A*pa=dynamic_cast(pd);//错误:二义性

B*pb=dynamic_cast(pd);//首先转换到B

A*pa2=dynamic_cast(pb);//ok:明确的

}

在使用虚基类的时候就导致更复杂的模糊;看下面的类层次图:

在这个继承层次中,A是虚基类。假定一个类E的实例并且一个指向A子对象的指针,一次

到B的dynamic_cast会由于不明确性导致失败,你必须首先转换到适当的层次,然后再向

上转换到确定的层次,一直按照这种方式直到到达正确的B对象。

看下面的类层次图:

假定一个类型E的对象和一个指向D子对象的指针,从D子对象导航到左上A子对象,必

须执行三个转换。从D到E的dynamic_cast的转换,然后一个从E到B的转换(可以是

dynamic_cast或者隐式转换),最终是从B到A的转换,例如:

voidf(D*pd)

{

E*pe=dynamic_cast(pd);//这里的D实际上是E类型的对象

B*pb=pe;//upcast,implicitconversion

A*pa=pb;//upcast,implicitconversion

}

交叉转换

dynamic_cast操作能执行交叉转换,使用上面相同的类层次,从B子对象到D子对象转

换是可能的,只要完整的对象是E。

由于交叉转换,从D指针到左上角A子对象的指针是可行的;首先从D到B的交叉转

换,然后隐式从B到A的转换。例如:

voidf(D*pd)

{

B*pb=dynamic_cast(pd);//crosscast

A*pa=pb;//upcast,implicitconversion

}

一个NULL指针值通过dynamic_cast转换到一个NULL指针。

当使用dynamic_cast(expression)时,如果expression不能安全的转换到type-id,

运行时检查导致转型失败,例如:

classA{...};

classB{...};

voidf()

{

A*pa=newA;

B*pb=dynamic_cast(pa);//fails,notsafe;BnotderivedfromA

...

}

转换失败的指针类型是NULL指针。失败的引用类型转换抛出bad_cast_exception异常;

如果expression没有指向或引用一个有效的对象将抛出__non_rtti_object异常。

再看下面看一个交叉转换例子:

structB1{virtual~B1(){}};

structB2{virtual~B2(){}};

structD1:B1,B2{};

intmain()

{

D1d;

B1*pb1=&d;

B2*pb2=dynamic_cast(pb1);//L1

B2*pb22=static_cast(pb1);//L2失败

return0;

}

上述定义中可以看到,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++风格的强制转型替换旧风格的强制转型。它们更容易被注意到,而且他们做的

事情也更加明确。

错误!未找到引用源。.reinpreter_cast

用法:reinpreter_cast(expression)

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

换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该

整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。

错误!未找到引用源。.const_cast

用法:const_cast(expression)

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

和expression的类型是一样的。

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

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

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

classB{

public:

intm_iNum;

}

voidfoo(){

constBb1;

b1.m_iNum=100;//comileerror

Bb2=const_cast(b1);

b2.m_iNum=200;//fine

}

上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast

把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的

对象。

更多推荐

dynamic_cast