条款20:宁以pass-by-reference-to-const替换pass-by-value
Prefer pass-by-reference-to-const to pass-by-value.
本章分为两个部分。
在一般的情况下,默认情况中C++会以by value的方式传递对象自(或来自)函数。除非我们去特别指定,否则函数参数都是以实际实参的复件为初值,而调用端获得的也是函数返回值的一个复件。
这些复件是由对象的copy构造函数生成的,这可能会造成pass-by-value称为较为费事儿的操作。
对于下面这个例子:
class Person {
public:
Person();
virtual ~Person();
...
private:
std::string name;
std::string address;
};
class Student:public Person {
public:
Student();
!Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
这时,我们调用函数validateStudent,该函数需要一个Student实参(by value)并返回它是否是有效的:
bool validateStudent(Student s); //函数以by value的形式接受学生
Student white; //定义一个学生white
bool whiteIsOk = validateStudent(white); //调用函数
当我们执行上面的代码时:
首先,Student的copy构造函数会被调用,以white为蓝本将s进行初始化。同时,当validateStudent返回s时会被销毁。因此,对于此函数而言,参数的传递成本是:
- 一次Student copy构造函数调用
- 一次Student析构函数调用
但是!这还不算完!Student对象内有两个string对象,因此每次构造一个Student对象也就构造了两个string对象。此外,Student对象继承自Person对象,因此,每次每次构造Student对象是也必须构造一个Person对象。一个Person对象又有两个string对象,因此,每一次Person的构造动作也要承担两个string对构造动作。
于是,最终结果是:
- 以by value的形式传递一个Student对象会调用一次Student对象会导致调用一次Student copy构造函数、一次Person copy构造函数、四次string copy构造函数。
因此,当函数内的Student复件被销毁时,每一个构造函数调用动作都会对应一个析构函数调用动作。
以by-value方式传递一个Student对象,总体成本是:
- 六次构造函数 和 六次析构函数!
这是一个非常大的代价了。想要回避这样的大代价的一个办法就是pass by reference-to-const:
bool validateStudent(const Student& s);
这样的传递方法,效率要高的多:
- 没有构造函数或者析构函数被调用,因为没有任何新对象被创建。
在上面修改后的代码,参数const是非常重要的。因为在原先的validateStudent中参数是以by-value的形式进行传递的,因此变相的就告诉我们这个传递的参数是收到保护的,函数内绝不会对传入的Student进行任何的更改,能更改也只是对Student的复件进行修改。
于是,Student以by reference方式的传递,将它声明为const是必要的,使得确保传递的Student不至于被修改。