Effective C++ 笔记三

当你谨慎的设计出 classes (template) 和 function template 之后, 你依旧应该注意一些细节上的东西.

  • 条款26: 太早的定义变量可能会导致效率上的拖延
  • 条款27: 过度的使用转型,可能导致代码变慢,难以维护,以及难以理解的错误
  • 条款28: 返回对象的内部数据的 handles ,可能会破坏封装,并留下 危险的handles
  • 条款29: 未考虑异常则可能导致资源的泄漏和数据的败坏
  • 条款30: 过度的 使用inline 可能引起代码的膨胀
  • 条款31: 过度的耦合则可能导致让人不满意的冗长建置时间(build times)

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

一旦你定义了一个变量,你就的为它付出代价.

1
2
3
4
5
6
7
int encrytPassword(const string& password) {
string encrypted;
if(password.size() == 0)
return -1;
//...
return encrypted;
}

传入的密码即使是空串,你依旧得付出 encrypted 的构造成本和析构成本,最好延后 encrypted 的定义式,直到确实需要它.

1
2
3
4
5
6
7
8
int encrytPassword(const string& password) {

if(password.size() == 0)
return -1;
string encrypted;
//...
return encrypted;
}

现在,你需要付出 default 构造函数,进行赋值,析构的成本. 而条款4曾指出 “通过default 构造函数构造出一个对象然后对它赋值” 比 “直接在构造时指定初值” 效率差.
因此更好的代码是跳过无意义的 default 构造过程.

1
2
3
4
5
6
7
8
int encrytPassword(const string& password) {

if(password.size() == 0)
return -1;
string encrypted(password);
//...
return encrypted;
}

“竟可能延后”的真正意思是: 你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,你还应该延后这份定义直到能够给它初值实参为止.

1
2
3
4
5
6
7
8
9
// 方法A: 定义于循环外
Widget w;
for(int i = 0; i < n; +++i) {
w = ..;
}
//方法B: 定义为循环内
for(int i = 0; i < n; +++i) {
Widget w(...);
}

我们现在来比较这两种方法:
方法A: 需要付出 1个构造函数 + 1个析构函数 + n 个 赋值操作
方法B: n个构造函数 + n 个 析构函数
如果 clases 的 一个赋值成本低于一个 构造+ 析构 成本,方法A更为高效(当 n 较大时,方法A更优).

27.尽量少做转型动作

C++ 总共提供六种形式的转型操作,其中包括两种 “旧式转型” 和四种 “新式转型”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*旧式转型*/

(T) expression
T(expression)
/*新式转型*/

// const 转换为 non-const
const_cast<T>( expression )
// 用来执行 "安全向下转型"
dynamic_cast<T> ( expression )
// 执行低级转型,例如将 一个 pointer to int 转型为 一个 int
reinterpret_cast<T> ( expression )
//用来强迫隐式转换,例如将 non-const 转换为const ,将 void* 指针转为 typed 指针.
static_cast<T> ( expression )

28. 避免返回 handles 指向对象内部成分

1
2
3
4
5
6
7
8
9
10
11
class Point{
//...
};
class Rectangle{
public:
Point& dosomething() const {
return p;
}
private:
Point p;
};

上面的代码返回了 Point 的引用,这实际上是自相矛盾的,函数被声明为 const,但调用者却依旧能够使用返回的 reference 修改成员变量.

同样的情况发生在指针,迭代器身上

1
2
3
4
5
6
7
8
9
//将代码更改为这样是一个好选择吗?
Point dosomething() const {
return p;
}
//还是这个?
//但这可能造成 dangling handles 问题
const Point& dosomething() const {
return p;
}

上面的方案一是昂贵的时间消耗,而方案二可能带来 dangling handles 问题.

29. 考虑 “出现异常”的情况

30. inlining

当你使用 inling 函数时, 你可以调用它们又不需要承担函数调用所招致的额外开销.除此之外,编译器还可能会对它进行特别优化.
但是,且慢,不要过度的使用 inlining ,这会造成程序体积的增大,导致频繁的换页行为,较低高速缓存的集中率.

1
2
3
4
5
6
7
8
9
10
class Person{
public:
//隐式声明为 inline
int age const( return age;)
bool sex() const;
};
//显式声明为 inline
inline bool Person::sex() const{
return ...;
}

例外,记住: inline 只是对编译器提出申请,并不是一个强制命令

31. 将文件间的编译依存关系将至最低