条款26 尽可能延后变量定义式的出现时间(Lazy evaluation)
记住:
★尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率
----------------------------------------------------------------------
举例说明:
std::string encryptPassword( const std::string &password ) { using namespace std; string encrypted; //过早定义变量“encrypted” if( password.length() < MininumLength ) { throw logic_error( "Password is too short" ); } ... return encrypted; }
一旦if语句中异常抛出,则上面的encrypted变量则未被使用,但仍得付出该变量的构造和析构成本,∴最好延后encrypted的定义式直到确实需要它。
更有甚,不只应延后变量的定义,甚至还应延后这份定义直到能给它初值为止,定义和赋初值一并完成,避免无谓的 先default构造 再赋初值。
对于循环的情况:
//方法A:定义于循环外 Widget w; for( int i=0; i<n; i++ ) { w = 取决于i的某个值; ... } //方法B:定义于循环内 for( int i=0; i<n; i++ ) { Widget w(取决于i的某个值); ... }
方法A 的成本:1个constructor + 1个destructor + n个assignment operate
方法B的成本:n个constructor + n个destructor
若赋值成本低于一组构造+析构成本,这样A大体而言交高效,否则B好。另外A造成w的作用域(覆盖整个循环)比B更大,有时那对程序的可理解性和易维护性造成冲突。
条款27 尽量少做转型动作
记住:
★若可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。若有个设计需要转型动作,试着发展无需转型的替代方案
★若转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内
★宁可使用C++风格转型(新式转型),不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌
---------------------------------------------------------------------------------------------------
两种旧式转型语法:
(T)expression
T(expression) //函数风格的转型动作
C++的四种新式转型:
const_cast<T>( expression ); //消除对象的常量性
dynamic_cast<T>( expression ); //安全向下转型(即向派生类端)
reinterpret_cast<T>( expression ); //执行低级转型
static_cast<T>( expression ); //强迫隐式转换
“我”(书作者)唯一使用旧式转型的情况是:当我要调用一个explicit构造函数(不允许隐式转换)将一个对象传递给一个函数时:
class Widget { public: explicit Widget( int size ); //ctor声明为explicit就不会导致隐式转换!!! ... }; void doSomeWork( const Widget &w ); doSomeWork( Widget(15) ); //以一个int加上“函数风格”的转型动作创建一个Widget
不要错误地认为转型其实什么都没做,只是告诉编译器把某个类型视为另一种类型。任何一个类型转化往往真的令编译器编译出运行期间执行的码。
------------------
关于转型的一个错误理解的例子:
假设有个Window基类和SpecialWindow派生类,进一步假设SpecialWindow的onResize被要求首先调用Window的onResize:
class Window { public: virtual void onResize() {...} ... }; class SpecialWindow : public Window { public: virtual void onResize() { static_cast<Window>(*this).onResize(); ... //这里进行SpecialWindow专属行为 } ... };
其实这句static_cast<Window>(*this).onResize();并非在当前对象身上调用Window::onResize之后又在该对象身上执行SpecialWindow专属动作。他是在“当前对象之base class成分”的副本上(!!!)调用Window::onResize,然后在当前对象身上执行SpecialWindow专属动作。这很有可能使当前对象出现“伤残”状态:其base class成分的更改没有落实,而derived class成分的更改倒是落实了。
解决方案如下:避免使用转换
class SpecialWindow : public Window { public: virtual void onResize() { Window::onResize(); ... } ... };
-----------------
除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_cast保持机敏与猜疑。∵dynamic_cast的许多实现版本执行速度相当慢!!!
-----------------
之所以需dynamic_cast,通常是因为想在一个derived class对象身上执行derived class函数,但手上仅一个指向base的pointer或reference,有两个一般性做法:
做法一:使用容器并在其中存储直接指向derived class对象的指针(通常是smart pointer)
假设先前的Window/SpecialWindow继承体系中仅SpecialWindow才支持闪烁效果:
不要这样做: class Window {...}; class SpecialWindow : public Window { public: void blink(); ... }; typedef std::vector< std::tr1::shared_ptr<Window> > VPW; VPW winPtrs; ... for( VPW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) { if( SpecialWindow *psw = dynamic_cast<SpecialWindow*>( iter->get() ) ) { psw->blink(); } } 而应这样做: typedef std::vector< std::tr1::shared_ptr<SpecialWindow> > VPSW; VPSW winPtrs; ... for( VPSW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) { (*iter)->blink(); //这样写比较好,∵摆脱了dynamic_cast }
做法二:在base class内提供virtual函数做你想对各个Window派生类做的事:
class Window { public: virtual void blink() {} //缺省实现代码,啥也没做 //条款34将说明缺省实现代码可能是个馊主意 }; class SpecialWindow : public Window { public: virtual void blink(); ... }; typedef std::vector< std::tr1::shared_ptr<Window> > VPW; VPW winPtrs; ... for( VPW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) { (*iter)->blink(); //也摆脱了dynamic_cast }
-------------------------------------------
绝对要避免“连串dynamic_cast”
typedef std::vector< std::tr1::shared_ptr<Window> > VPW; VPW winPtrs; ... for( VPW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) { if( SpecialWindow1 *psw1 = dynamic_cast<SpecialWindow1*>(iter->get()) ) {...} else if( SpecialWindow2 *psw2 = dynamic_cast<SpecialWindow2*>(iter->get()) ) {...} else if( SpecialWindow3 *psw3 = dynamic_cast<SpecialWindow3*>(iter->get()) ) {...} ... }
这样产生出来的代码又大又慢,且基础不稳。
条款28 避免返回handles指向对象内部成分
记住:
★避免返回handles(包括reference,指针,迭代器)指向对象内部。遵守这个条款可增加封装性;帮助const成员函数的行为像个const;并将发生“虚吊号码牌”的可能性降到最低。
-------------------------------------------------------------------------