EffectiveC++笔记 第5章

我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的“可能比较准确”的「翻译」。

Chapter 5 实现 Implementations

适当提出属于你的class定义以及各种functions声明相当花费心思。一旦正确完成它们,相应的实现大多直截了当。尽管如此,还是要小心很多细节。

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

当你定义了一个变量,其类型带有构造函数和析构函数,当程序控制流(control flow)到达此变量定义式时,你需要承担构造成本;此变量离开作用域时,便需要承担析构成本———即使你自始至终都没有用过它。所以你应避免这种情况

你会问:怎么可能定义「不被使用的变量」?下面考虑一个函数,作用是计算通行密码的加密版本后返回,但前提是密码足够长。若太短,函数会抛出异常,类型为logic_error

//此函数过早定义变量“encrypted”
std::string encryptPassword(const std::string& password)
{
    using namespace std;
    string encrypted;
    if(password.length()<MinimumPasswordLength){
        throw logic_error("Password is too short");
    }
    ... //诸如将加密后的密码放进encrypted内的动作
}

这里存在的问题是,如果抛出了异常,那encrypted就真的没被使用———然而你还得付出构造和析构的成本。 看起来较好的解决方案是这样的:

...
if(password.length()<MinimumPasswordLength){
throw logic_error("Password is too short");
}
string encrypted; //延后定义式,直到真正需要它
...

其实效率不够高,因为encrypted虽获定义却无实参作初值。更好的做法是“直接在构造时指定初值”,这样的效率高于default构造函数(构造对象再对它赋值)。<我们在条款4讨论过效率问题>

现在我们一步一步进行分析。假设将函数encryptPassword的加密部分用 void encrypt(std::string& s); 实现,于是encryptPassword实现如下:

//此版本虽延后了定义,但仍效率低下:
std::string encryptPassword(const std::string& password)
{
    ...    //检查length,同前
    std::string encrypted; //default constructor,无意义
    encrypted = password;
    encrypt(encrypted);
    return encrypted;
}

更受欢迎的做法是直接将password作为encrypted初值,跳过无意义默认构造:

std::string encrypted(password);

现在我们大概能理解「尽可能延后」的深层含义:你应尝试延后这份变量定义直到能够给它初值实参为止。

但遇到循环怎么办?若我们只在循环内用到变量,是该将它定义与循环外并在每次循环迭代赋值给它,还是将其定义于循环内? :

//A方案,定义于循环外:       //方法B,定义于循环内 :
Widget w;                  for(int i=0;i<n;++i){
for(int i=0;i<n;++i){          Widget w(表达式取决于i值);
    w = 表达式;(取决于i值)      ...
}                             }

首先看A和B做法的成本:

  • A: 1个构造函数 + 1个析构函数 + n个赋值操作
  • B: n个构造函数 + n个析构函数

我们可以理清:

  1. A的适用情况:
    class的一个赋值成本低于一组构造+析构成本 ;否则做法B较好

  2. 另外,A造成名称w作用域大于B,有潜在对程序可理解性和易维护性的冲突。

结论

除非你知道赋值成本小于“析构+构造” ;
你正在处理代码中对性能高度敏感(performance-sensitive)部分。
否则你该使用做法B。

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

很不幸,转型(casts)可能导致各种麻烦,有的显而易见,有的非常隐晦。

让我们复习一下转型的语法:

  • C风格:
    (T)expression 将_expression_转为T
  • 函数风格:
    T(expression) 将_expression_转为T

它们并无差别,只是小括号位置不同而已。我们可以成这两种为「旧式转型」(old-style casts)。

C++还提供了四种新式转型:

  1. const_cast
  2. dynamic_cast
  3. reinterpret_cast
  4. static_cast

各有不同用途:

  • const_cast通常用来将对象的常量性质除去(cast away the constness)(不是真正除去)。它是唯一有此能力的C++-style转型操作符。

  • dynamic_cast主要用作“ 安全向下转型 safe downcasting ”,能决定对象是否属于继承体系中某个类型。它是唯一无法用旧式语法执行的动作,并可能耗费大量运行成本。

  • reinterpret_cast执行低级转型,实际动作及结果取决于编译器,这意味它不可移植。

  • static_cast用来强迫隐式转换(implicit conversions)。例如将non-const对象转为const对象,将int转为double等。但将const转为non-const只有const_cast做得到

新式转型较受欢迎:

  1. 它们易被辨识(不论人工还是工具)
  2. 可以缩小转型动作的选择范围。比如想去掉常量性(constness),只有const_cast能办到

使用旧式转型一般是调用explicit构造函数将对象传给一个函数:

class Widget{
public:
    explicit Widget(int size);
    ...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15));       //函数式
doSomeWork(static_cast<Widget>(15));   //新式

使用第一种的原因可能是你觉得比较这样自然,不像第二种蓄意的“生成对象”。但是为了以后代码的可靠性,还是老老实实用新式转型吧。

持续更新中................................

相关推荐