Effective C++读书笔记(16)(一)

2014-11-24 12:20:06 · 作者: · 浏览: 3
第15篇 http://www.2cto.com/kf/201202/118370.html

条款26:尽可能延后变量定义式出现的时间

Postpone variable definitions as long as possible

只要你定义了一个带有构造函数和析构函数的类型变量,当控制流程到达变量定义时,你会承受构造成本,而当变量离开作用域时,你会承受析构成本。如果有最终并未被使用的变量造成这一成本,你就要尽你所能去避免它。

不要认为自己不会定义一个不使用的变量。考虑下面这个函数,它计算通行密码的 加密版本然后返回,前提是密码够长。如果密码太短,函数就会抛出一个定义在C++标准程序库中的logic_error类型异常:

// 这个函数过早定义变量encrypted
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() throw logic_error("Passwordis too short");
}
... // 必要动作,将一个加密后的密码置入变量encrypted
return encrypted;
}

如果抛出了一个异常,对象encrypted在这个函数中就是无用的。换句话说,即使encryptPassword抛出一个异常,你也要为构造和析构encrypted付出代价。因此你最好将encrypted的定义推迟到你确信你真的需要它的时候,即判断是否会抛出异常之后。

这还不够,因为定义encrypted的时候没有任何初始化参数。这就意味着很多情况下将使用它的缺省构造函数。缺省构造一个对象然后赋值比用你真正需要它持有的值初始化它效率更低。例如,假设encryptPassword的核心部分是在这个函数中完成的:

voidencrypt(std::string& s); // 在其中的适当地点对s加密

那么,encryptPassword可实现如下,即使它还不是最好的方法:

std::stringencryptPassword(const std::string& password)
{
... // 检查length,如前
std::string encrypted; // default-construct encrypted
encrypted = password; // 赋值给encrypted
encrypt(encrypted);
return encrypted;
}

更可取的方法是用password初始化encrypted,从而跳过毫无意义并可能很昂贵的缺省构造:

std::stringencrypted(password); // 通过copy构造函数定义并初始化

这就是本条款的标题的真正含义。你不仅应该推迟一个变量的定义直到你不得不用它的最后一刻,而且应该试图推迟它的定义直到得到了它的初始化参数。通过这样的做法,你可以避免构造和析构无用对象,而且还可以避免不必要的缺省构造。



对于循环,如果一个变量仅仅在一个循环内使用,是循环外面定义它并在每次循环迭代时赋值给它更好,还是在循环内部定义这个变量更好?

//Approach A: 定义于循环外
Widget w;
for (int i = 0; i < n; ++i){
w = 取决于i的某个值;
...
}

// Approach B: 定义于循环内
for (int i = 0; i < n; ++i) {
Widget w(取决于i的某个值);
...
}

对于Widget的操作而言,就是下面这两个方法的成本:

· 方法A:1个构造函数+ 1个析构函数+ n个赋值。

· 方法B:n个构造函数+ n个析构函数。

对于那些赋值的成本低于一个构造函数/析构函数对的成本的类,方法A通常更高效,特别是在n变得很大的情况下。否则,方法B可能更好一些。此外,方法A与方法B相比,使得名字w 在一个较大的区域(包含循环的那个区域)内均可见,这可能会破坏程序的易理解性和可维护性。因此得出结论,除非你确信以下两点:(1)赋值比构造函数/析构函数对成本更低,(2)你正在涉及你代码中性能敏感的部分,否则你应该默认使用方法B。

· 尽可能延后变量定义式的出现。这样可以增加程序的清晰度并提高程序的性能。



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

Minimize casting

强制转型破坏了类型系统。它会引起各种各样的麻烦,其中一些容易被察觉,另一些则格外地隐晦。强制转型在C,C#和 Java中比在C++中更有必要,危险也更少。在C++语言中,强制转型是一个必须全神贯注的特性。

C风格(C-style)强制转型如下:

(T) expression // 将expression转型为T

函数风格(Function-style)强制转型使用这样的语法:

T(expression) // 将expression转型为T

这两种形式之间没有本质不同,纯粹就是一个把括号放在哪的问题。这两种形式被称为旧风格(old-style)的强制转型。

C++ 同时提供了四种新的强制转型形式(通常称为新风格的或C++风格的强制转型):

(因对强制转换不熟悉,且书上的解释自觉不够清晰,以下内容多摘自网络)

1)static_cast < type-id > ( expression )

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。编译器隐式执行任何类型转换都可由static_cast显示完成。它主要有如下几种用法:

  ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换:

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

  进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

  ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

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

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

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



2)dynamic_cast < type-id > ( expression )

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

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

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

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全