chapter 5:实现
item26:尽可能延后变量的定义
1)如果一个函数中可能抛出异常,那么在确定你必须用这个变量的时候定义这个变量,比如:
//变量放在异常捕捉之前,如果有异常抛出,那定义的这个变量就没有被使用过。
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
...// do whatever is necessary to place an encrypted version of password in encrypted
return encrypted;
}
//变量放在异常捕捉之后,避免不必要的定义
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
...// do whatever is necessary to place an encrypted version of password in encrypted
return encrypted;
}
2)既然定义了一个变量,肯定是要用它的。那么如果先定义再赋值的开销是调用默认构造函数和赋值函数,这肯定比直接调用拷贝构造函数的开销大。所以在变量定义的时候就用copy构造函数初始化它,避免默认构造函数的调用。
3)如果是对于循环的情况,考虑下面两种代码,一种是将变量定义放在循环体外,一种是将变量直接放在循环体内定义。
//方法A,开销是1个构造函数+1个析构函数+n个赋值函数
Widget w;
for (int i = 0; i < n; ++i)
{
w = some value dependent on i;
...
}
//方法B,开销是n个构造函数+n个析构函数
for (int i = 0; i < n; ++i)
{
Widget w(some value dependent on i);
...
}
到底哪一个方法更好呢?除非预先知道赋值的成本比"构造+析构"成本低,或者说你正在处理代码中效率高度灵敏的部分,否则应该使用方法B。
item27 尽量少做转型
1)首先应该明白转型破坏了类型系统
//C风格的转型,两种方式,等价的
(T) expression or (T) expression
//C++提供的新式转型
const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)
首先了解C++新式转型用途:
const-cast:解除对象的constantness。
dynamic-cast:提供安全的向下转型。比如基类转化为派生类。
reinterpret-cast:执行低级转型,实际动作可能取决于编译器,这也表示它不可移植。比如说将一个指针转化成int。
static_cast:强制类型转化,比如说int转成double,或者non-const转成const。
如果不得不用转型的时候,最后用C++的新式转型,因为它更容易被用户或者编译器识别,当然每种转型也更有针对性,更容易诊断出错误的应用。
2)任何一个类型转换往往令编译器编译出运行期间执行的代码。
class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d; // implicitly convert Derived* to Base*
这里建立了一个基类指针指向派生类的对象,但是有的时候上述两个指针值并不相同。这种情况下会有个偏移量(offset)在运行期间被施行于派生类指针的身上,用来取得正确的基类的指针值。这说明单一对象(比如刚刚说的派生类的对象)可能拥有一个以上的地址(以基类指针指向它时的地址和以派生类指针指向它时的地址)。(看到这里其实我想起来微软今年实习生的那道笔试题,可能原理还是没能理得太清楚)。实际上,一旦使用多重继承,这事几乎一直发生着,即使在单一继承中也可能发生。当然这里说的只是有可能有那个偏移量。对象的布局方式和它们的地址计算方式随编译器的不同而不同。
3)这里假设一个应用框架,在派生类中优先调用基类的onResize函数,看起来下面的实现方式是对的,实际上错了:
class Window { // base class
public:
virtual void onResize() { ... } // base onResize impl
...
};
class SpecialWindow: public Window { // derived class
public:
virtual void onResize() // derived onResize impl;
{
//cast *this to Window,then call its onResize;this doesn't work!do SpecialWindow-specific stuff。
static_cast<Window>(*this).onResize();
...
}
...
};
class SpecialWindow: public Window {
public:
virtual void onResize() {
//回想当时写MFC程序的时候,C++重载某个基类的函数时,都会默认的加上这一句。
Window::onResize(); // call Window::onResize on *this
...
}
...
};
实际上函数里面调用的只是当前对象转型后的一个window版本的副本,假设在调用window::onResize中做了某些修改,那跟当前的对象没半毛钱的关系。但是如果在后续中调用了SpecialWindow:onResize,并且也做出了修改,那这个修改就起作用了,但是这样只有部分修改起作用的结果就是原始的数据已经被破坏了。所以,把转型的动作替换成你想要实现的代码。
4)关于dynamic-cast另外需要注意的一点,dynamic-cast的许多实现版本执行速度都相当慢。至少一个普遍的实现版本是基于"class名称的字符串比较的。如果是四层深的单继承体系内某个对象身上执行dynamic-cast,那可能需要调用四次的strcmp函数。如果是多重继承,那成本更高。所以在避免使用转型的前提下,特别的避免使用dynamic-cast。
5)之所以需要dynamic-cast,是因为想在一个认定为派生类对象的身上执行派生类的操作函数,假设传进来的是一个指向基类的指针,那就必须要转换了。有两种一般性的作法可以避免,那就是使用容器并在其中存储直接指向派生类对象的指针(或者以前介绍的智能指针)。假设只有SpecialWindows有blink方法。
typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
...
// better code: uses no dynamic_cast
for (VPSW::iterator iter = winPtrs.begin();iter != winPtrs.end();++iter)
(*iter)->blink();
当然这样的话,就不能在同一个容器中存储多种派生类的指针,或者说你需要多个容器。
6)当然,另一种方法就是在基类中提供一个虚函数,不做任何事,在派生类中定义想做的事,这就是常说的多态。
class Window {
public:
// default impl is no-op;see Item 34 for why a default impl may be a bad idea
virtual void blink() {}
...
};
class SpecialWindow: public Window {
public:
virtual void blink() { ... };// in this class, blink does something
...
};
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
// container holds (ptrs to) all possible Window types
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin();iter != winPtrs.end();++iter)
(*iter)->blink(); // note lack of dynamic_cast
//多态对避免下面连串dynamic-cast的发生也很有效果
class Window { ... };
... // derived classes are defined here
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())) { ... }
...
}
7)如果实在需要转型,那就将他隐藏在某个函数的本后。用户可以调用该函数,而不需要将转型放进他们自己的代码中。