【Effective C++】实现
大多数情况下,适当提出你的 classes(和 class templates)定义以及 functions(和 function templates)声明,是花费最多心力的两件事。一旦正确完成它们,相应的实现大多直截了当。尽管如此,还是有些东西需要小心。太快定义变量可能造成效率上的延迟;过度使用转型(casts)可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象 “内部数据之号码牌(handles)” 可能会破坏封装并留给客户虚吊号码牌(dangling handles);未考虑异常带来的冲击则可能导致资源泄露和数据破坏;过度热心地 inlining 可能引起代码膨胀;过度耦合(couping)则可能导致让人不满意的冗长建置时间(build times)。
所有这些问题都可避免。本章足以解释各种做法。
条款26:尽可能延后变量定义式的出现时间
只要定义一个变量而其类型带构造或者析构函数,那就要承担构造或者析构的成本,即使是这个变量没有被使用,比如:
std::string encryptPassword(const std::string & password) { using namespace std; string encrypted; if(password.length() < MinimumPasswordLength) { throw logic_error("Password is too short"); } ... return encrypted; }
如果有异常抛出,encrypted
就完全没有被使用,此时你仍得付出encrypted
的构造成本和析构成本。且使用默认构造函数出一个对象然后对它赋值效率比较差,更好的做法是:以password
作为encrypted
的初值,跳过毫无意义的默认构造过程。
std::string encryptPassword(const std::string & password) { using namespace std; if(password.length() < MinimumPasswordLength) { throw logic_error("Password is too short"); } string encrypted(password); ... return encrypted; }
尽可能延后的真正意义是:你不应该只延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义知道能够给它初值实参为止。
但是循环怎么办?考虑下面两个结构,哪一个比较好?
//做法A:定义在循环外,每次循环迭代时赋值给它 Widget w; for(int i = 0; i < n; ++i) { w = 取决于i的某个值 ... } //做法B:定义在循环内 for(int i = 0; i < n; ++i) { Widget w(取决于i的某个值); ... }
做法A:1个构造函数 + 1个析构函数+ n个赋值操作
做法B : n个构造函数+n个析构函数
如果赋值操作低于构造和析构成本,A比较好,尤其当n比较大的时候,否则做法B可能会更好。此外做法A造成w的作用域比做法B更大,有时会对程序的可理解性和易维护性造成冲突。
条款27:尽量少做转型动作
- 旧式C风格转型:
- (T)expression T(expression)
- 新式转型:
const_cast<T>(expression)//通常用来将对象的常量性移除. dynamic_cast<T>(expression)//主要用来执行安全向下转型,也就是用来决定某个对象是否归属继承体系中的某个类型. reinterpret_cast<T>(expression)//意图执行低级转型,比如将一个int*
转换成一个int
static_cast<T>(expression)//用来强迫隐式转换,比如将no-const对象转换为const对象,将int转为double等等.
旧式转型仍然合法,但新式转型比较受欢迎.原因是:
1.它们很容易在代码中被辨识出来
2.转型动作的目标越窄化,编译器越可能诊断出错误的运用。比如:如果你打算将常量性去掉,除非使用const-cast否则无法通过编译。
旧式转型使用的时机通常在explicit构造函数中。比如:
class Person { public: explicit Person(int age);//explicit防止类构造函数的隐式自动转化 } ... void doSomeWork(const Person& p); Person p = 21;//错误 Person p(21);//可以 doSomeWork(Person(15));//√ 感觉更通情达理 doSomeWork(static_cast<Person>(15));//√ 但是用新式转型更好
总结
1.尽量避免转型,特别是在注重效率的代码中避免dynamic-cast。如果有个设计需要转型动作,试着发展不需要转型的替代设计。
2.如果转型是必要的,试着将它隐藏于某个函数背后,将转型动作隔离,客户随后可以调用该函数,而不需将转型放进它们的代码内。
3.宁可使用C++风格(新式)的转型,也不要使用旧式转型。