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个析构函数
我们可以理清:
A的适用情况:
class的一个赋值成本低于一组构造+析构成本 ;否则做法B较好另外,A造成名称w作用域大于B,有潜在对程序可理解性和易维护性的冲突。
结论
除非你知道赋值成本小于“析构+构造” ;
你正在处理代码中对性能高度敏感(performance-sensitive)部分。
否则你该使用做法B。
条款27: 尽量少做转型动作 Minimize casting
很不幸,转型(casts)可能导致各种麻烦,有的显而易见,有的非常隐晦。
让我们复习一下转型的语法:
- C风格:
(T)expression 将_expression_转为T - 函数风格:
T(expression) 将_expression_转为T
它们并无差别,只是小括号位置不同而已。我们可以成这两种为「旧式转型」(old-style casts)。
C++还提供了四种新式转型:
- const_cast
- dynamic_cast
- reinterpret_cast
- 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做得到
新式转型较受欢迎:
- 它们易被辨识(不论人工还是工具)
- 可以缩小转型动作的选择范围。比如想去掉常量性(constness),只有const_cast能办到
使用旧式转型一般是调用explicit构造函数将对象传给一个函数:
class Widget{ public: explicit Widget(int size); ... }; void doSomeWork(const Widget& w); doSomeWork(Widget(15)); //函数式 doSomeWork(static_cast<Widget>(15)); //新式
使用第一种的原因可能是你觉得比较这样自然,不像第二种蓄意的“生成对象”。但是为了以后代码的可靠性,还是老老实实用新式转型吧。
持续更新中................................