AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > C++

C++箴言:将强制转型减到最少

51自学网 http://www.wanshiok.com

 

  我突出了代码中的强制转型。(这是一个新风格的强制转型,但是使用旧风格的强制转型也于事无补。)正像你所期望的,代码将 *this 强制转型为一个 Window。因此调用 onResize 的结果就是调用 Window::onResize。你也许并不期待它没有调用当前对象的那个函数!作为替代,强制转型创建了一个 *this 的基类部分的新的,临时的拷贝,然后调用这个拷贝的 onResize!上面的代码没有调用当前对象的 Window::onResize,然后再对这个对象执行 SpecialWindow 特有的动作——它在对当前对象执行 SpecialWindow 特有的动作之前,调用了当前对象的基类部分的一份拷贝的 Window::onResize。如果 Window::onResize 改变了当前对象(可能性并不小,因为 onResize 是一个 non-const 成员函数),当前对象并不会改变。作为替代,那个对象的一份拷贝被改变。如果 SpecialWindow::onResize 改变了当前对象,无论如何,当前对象将被改变,导致的境况是那些代码使当前对象进入一种病态,没有做基类的变更,却做了派生类的变更。

  解决方法就是消除强制转型,用你真正想表达的来代替它。你不应该哄骗编译器将 *this 当作一个基类对象来处理,你应该调用当前对象的 onResize 的基类版本。就是这样:   

  class SpecialWindow: public Window {

  public:

  virtual void onResize() {

  Window::onResize(); // call Window::onResize

  ... // on *this

  }

  ...   

  };

  这个例子也表明如果你发现自己要做强制转型,这就是你可能做错了某事的一个信号。在你想用 dynamic_cast 时尤其如此。

  在探究 dynamic_cast 的设计意图之前,值得留意的是很多 dynamic_cast 的实现都相当慢。例如,至少有一种通用的实现部分地基于对类名字进行字符串比较。如果你在一个位于四层深的单继承体系中的对象上执行 dynamic_cast,在这样一个实现下的每一个 dynamic_cast 都要付出相当于四次调用 strcmp 来比较类名字的成本。对于一个更深的或使用了多继承的继承体系,付出的代价会更加昂贵。一些实现用这种方法工作是有原因的(它们不得不这样做以支持动态链接)。尽管如此,除了在普遍意义上警惕强制转型外,在性能敏感的代码中,你应该特别警惕 dynamic_casts。

  对 dynamic_cast 的需要通常发生在这种情况下:你要在一个你确信为派生类的对象上执行派生类的操作,但是你只能通过一个基类的指针或引用来操控这个对象。有两个一般的方法可以避免这个问题。

  第一个,使用存储着直接指向派生类对象的指针的容器,从而消除通过基类接口操控这个对象的需要。例如,如果在我们的 Window/SpecialWindow 继承体系中,只有 SpecialWindows 支持 blinking,对于这样的做法:   

  class Window { ... };   

  class SpecialWindow: public Window {

  public:

  void blink();

  ...

  };

  typedef // see Item 13 for info

  std::vector > VPW; // on tr1::shared_ptr   

  VPW winPtrs;

  ...   

  for (VPW::iterator iter = winPtrs.begin(); // undesirable code:

  iter != winPtrs.end(); // uses dynamic_cast

  ++iter) {

  if (SpecialWindow *psw = dynamic_cast(iter->get()))

  psw->blink();

  }

  设法用如下方法代替:   

  typedef std::vector > VPSW;   

  VPSW winPtrs;   

  ...   

  for (VPSW::iterator iter = winPtrs.begin(); // better code: uses

  iter != winPtrs.end(); // no dynamic_cast

  ++iter)

  (*iter)->blink();

  当然,这个方法不允许你在同一个容器中存储所有可能的 Window 的派生类的指针。为了与不同的窗口类型一起工作,你可能需要多个类型安全(type-safe)的容器。

  一个候选方法可以让你通过一个基类的接口操控所有可能的 Window 派生类,就是在基类中提供一个让你做你想做的事情的虚函数。例如,尽管只有 SpecialWindows 能 blink,在基类中声明这个函数,并提供一个什么都不做的缺省实现或许是有意义的:   

  class Window {

  public:

  virtual void blink() {} // default impl is no-op;

  ... // see Item 34 for why

  }; // a default impl may be

  // a bad idea   

  class SpecialWindow: public Window {

  public:

  virtual void blink() { ... }; // in this class, blink

  ... // does something

  };   

  typedef std::vector > VPW;   

  VPW winPtrs; // container holds

  // (ptrs to) all possible

  ... // Window types   

  for (VPW::iterator iter = winPtrs.begin();

  iter != winPtrs.end();

  ++iter) // note lack of

  (*iter)->blink(); // dynamic_cast

  无论哪种方法——使用类型安全的容器或在继承体系中上移虚函数——都不是到处适用的,但在很多情况下,它们提供了 dynamic_casting 之外另一个可行的候选方法。当它们可用时,你应该加以利用。

  你应该绝对避免的一件东西就是包含了极联 dynamic_casts 的设计,也就是说,看起来类似这样的任何东西:   

  class Window { ... };   

  ... // derived classes are defined here   

  typedef std::vector > VPW;   

  VPW winPtrs;   

  ...   

  for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)

  {

   if (SpecialWindow1 *psw1 = dynamic_cast(iter->get())) { ... }   

   else if (SpecialWindow2 *psw2 = dynamic_cast(iter->get())) { ... }   

   else if (SpecialWindow3 *psw3 = dynamic_cast(iter->get())) { ... }   

  ...

  }

  这样的 C++ 会生成的代码又大又慢,而且很脆弱,因为每次 Window 类继承体系发生变化,所有这样的代码都要必须被检查,以确认是否需要更新。(例如,如果增加了一个新的派生类,在上面的极联中或许就需要加入一个新的条件分支。)看起来类似这样的代码应该总是用基于虚函数的调用的某种东西来替换。 好的 C++ 极少使用强制转型,但在通常情况下完全去除也不实际。例如,从 int 到 double 的强制转型,就是对强制转型的合理运用,虽然它并不是绝对必要。(那些代码应该被重写,声明一个新的类型为 double 的变量,并用 x 的值进行初始化。)就像大多数可疑的结构成分,强制转型应该被尽可能地隔离,典型情况是隐藏在函数内部,用函数的接口保护调用者远离内部的污秽的工作。

  Things to Remember

  ·避免强制转型的随时应用,特别是在性能敏感的代码中应用 dynamic_casts,如果一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。

  ·如果必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。

  ·尽量用 C++ 风格的强制转型替换旧风格的强制转型。它们更容易被注意到,而且他们做的事情也更加明确。

 
 

上一篇:C++箴言:理解inline的介入和排除  下一篇:xcopy32完整实现