条款27:尽量少做转型动作

本文深入探讨C++中的转型操作,包括旧式转型和四种新式转型(const_cast、dynamic_cast、reinterpret_cast、static_cast)。文章对比了新旧式转型的特点,并详细解释了dynamic_cast的工作原理及其效率问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++的设计目标之一是保证“类型错误”绝不会发生。但转型(cast)破坏了类型系统(type system).
旧式转型,C风格的转型动作,如下:
	(T)tmp     //将tmp转型为T
	T(tmp)     //同上,函数风格的转型动作

C++提供四种新式转型(new-style或C++-style casts)
	1、const_cast<T> (tmp)   通常用来将对象的常量性转除,也是唯一有此能力的新式转型操作符。
	2、dynamic_cast<T>(tmp)   主要用来执行“安全向下转型”,决定某对象是否归属继承体系中的某个类型。唯一无法由旧式语法执行的动作唯一可能耗费大运行成本的动作。
	3、reinterpret_cast<T> (tmp)   低级转型,实际可能取决于编译器。很少用。
	4、static_cast<T>(tmp)  强迫隐式转型(implict conversion),如int ->double,void*->type指针。但无法将const->nonconst,第一点已经提及。
 
	新式比旧式转型更受欢迎,主要有:1、代码中更容易识别  2、转型动作目标窄,编译器越可能诊断出错误。如const->non const 除非使用const_cast.  否则无法通过
 
使用旧式转型的例子,调用explict构造函数将一个对象传递给个函数,如下:
class widget
{
         explictwidget(int size);
         ...
};
void fun(const widget& w);
fun(widget(8));   //旧式转型,“函数风格“的转型动作创建一个widget
fun(static_cast<widget>(8));  //新式风格转型动作创建一个widget
 
任何一个类型转换(无论隐式或显示),往往令编译器编译出运行期间的执行代码,如下:
int   x,y;
double d=static_cast<double>(x)/y;
x->y转型肯定会产生一些代码。因为int和double底层描述不同。

再如下:
class Base{};
class Derived: public Base{};
Derived d;
Base* pb=&d;

基类指针指向派生类对象,有时上述两指针值并不相同。这时会有偏移量加于Derived*身上,以取得正确的Base*。以上表明单一对象可能拥有一个以上的地址。(如base和derived指向它的地址)
 
考虑以下代码,很多应用框架要求derived  class的虚函数的代码第一个动作是调用base class的对应函数,很容易写出似是而非的代码:
//错误做法
class Wnd
{
  virtual voidsize(){...};  //基类size实现代码
  ...
};
class Cwnd: public Wnd
{
  virtual void size()
  {
         static_cast<Wnd>(*this).size();   //*this转型为Wnd,调用其size();
         ...    //derive专属行为
  }
};

虽然将“*this ”转型为Wnd,该代码调用的并不是当前对象上的函数,而是转型动作所建立的“*this对象的基类成分”的暂时副本上的函数size()
	最终结果是:未作用于当前对象,当前对象的base成分没落实(如果Wnd::size()修改了对象内容),仅仅变动了derive类部分。
//正确做法,去掉转型操作
class Cwnd: public Wnd
{
  virtual void size()
  {
         Wnd::size();   //调用Wnd::size()作用于*this上
         ...   //derive专属行为
  }
};
 
dynamic_cast的实现版本执行效率相当慢,如一个基于“class名称字符串的比较”的实现版本,如果在第四层单继承体系内某个对象执行dynamic_cast,则可能会出现多大家四次strcpy调用。所以应在注重效率的代码中对dynamic_cast保持慎重。
 
之所以用dynamic_cast,是在操作派生类对象时,而手头却只有指向其基类对象的指针或引用。so只能靠它来处理,主要有两种方式避免这问题:
1、使用容器存储指向派生类对象的指针(通常为智能指针),从而消除“基类指针处理派生类对象的需要”

//不应该这么做
class Wnd{....};
class Cwnd: public Wnd
{
  public:
  void fun1();  //只有类Cwnd支持函数fun1()
  ...
};
 
typedefstd::vector<std::tr1::shared_ptr<Wnd>>  VPW;
VPW wp;
...
for(VPW::iterator it=wp.begin();it!=wp.end;++it)
  if(Cwnd*pcw=dynamic_cast<Cwnd*>(it->get())) //使用dymanic_cast转型
   pcw->fun1();
 
 
//而应该这么做
typedef std::vector<std::tr1::shared_ptr<Cwnd>>VPCW;
VPCW cwp;
...
for(VPCW::itertor it=cwp.begin();it!=wp.end;++it)  //不使用dymanic_cast转型
  *it->fun();

这种做法的缺点是,无法用一个容器指向多个派生类。如果要处理多种类型,就必须要多个容器。
 
2、方法二:基类接口处理各派生类,即基类提供虚函数,缺省什么都不做,如下代码:
class Wnd
{
  public:
 virtual voidfun(){};   //缺省什么也不做
  ...
};
 
class Cwnd: public Wnd
{
  public:
         virtual voidfun(){....}; //派生类的专属实现
  ...
};
typedef std::vector<std::tr1::shared_ptr<Wnd>>PW;   //内含指针,指向所有可能的Wnd类型
PW wp;
...
for(PW::iterator it=wp.begin();it!=wp.end();++it)  //没有使用dynamic_cast
  (*it)->fun();

以上两种都是可行的dynamic_cast替代方案,如果有功效时我们就该使用。
 
绝对要避免的一件事是使用一连串的dynamic_cast,这样的代码又大且慢,并基础不稳。当继承体系改变时要再次检阅代码。应用“基于虚函数的调用代替”。
 
需要记住的:
1、尽量避免转型,在注重效率的代码中避免dynamic_cast,如果需要转型操作,试着以无需转型的操作替代。
2、如果转型必要,将其隐藏于某个函数。客户可以调用,但无需将转型放入自己代码中。
3、宁可使用新式,避免使用旧式转型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值