条款20: 宁以pass-by-reference-to-const 替换 pass-by-value
1、为什么要宁以pass-by-reference-to-const 替换 pass-by-value
效率方面
缺省情况下,C++以by value 方式传递对象至(或来自)函数。
除非你另外指定,否则函数参数都是以实际实参的副本为初值,而调用段所获得的亦是函数返回值的一个副本。这些副本都是由对象的copy构造函数产出的,这可能使得pass-by-value 称为昂贵的(费时的)操作。
考虑下述例子:
class Person {
public:
Person();
virtual ~Person();
...
private:
string name;
string address;
};
class Student: public Person {
public:
Student();
~Student();
...
private:
string schoolName;
string schoolAddress;
};
bool validateStudent(Student s);
Student plato;
//传递参数方式一
bool platoIsOK = validateStudent(plato);
//传递参数方式二
bool validateStudent(const Student& s);
以方式一传递参数时,由于C++默认的是by value 的方式,因此,会构造一个Student对象,并且以plato进行初始化。这会导致调用Student的构造函数,而当函数返回时,势必又要调用其析构函数。 这只是表面,由于Student继承自Person,因此在创建Student时,势必要调用其基类的构造函数来初始化其基类部分,销毁时也是同样的。而又由于Person 和Student类中又内涵string对象,因此在构建时,又要调用string对象的构造和析构函数。全部算在一起,一共会调用6次析构函数和6次构造函数。这是非常耗时的操作。
而避免上述耗时的方法也非常简单,即以pass-by-value-to-const方式传递参数即可。这样的传递方式不会有任何新的对象被创建,因此也不会调用析构函数和构造函数。
2、第二个原因,避免slicing(对象切割)问题
(1)什么是对象切割问题?
一个函数的形参,接收的是一个基类的对象。 但如果调用这个函数时,传递的是一个派生类对象的话,那么形参只会构造这个派生类对象的基类部分作为实参。因此,当在函数内使用这个派生类对象时,它的所有被特化的部分都会表现的是基类的特性。
举栗子:
class Window {
public:
...
string name() const;
virtual void display() const;
};
class WindowWithScrollBars: public Window {
public:
...
virtual void display() const;
};
void printNameAndDisplay(Window w) //造成对象切割问题
{
cout << w.name();
w.display();
}
//解决办法
void printNameAndDisplay(const Window& w)
{
cout << w.name();
w.display();
}
(2)解决的方法
参数以pass-by-reference-to-const 方式传递。
3、pass-by-reference-to-const和pass-by-value的区别,及适用场景?
pass-by-reference-to-const 的底层实现是指针,pass-by-reference-to-const方式通常意味着传递的是指针。因此,如果是内置类型的参数,pass-by-value比pass-by-reference-to-const的效率更高一些。同样的,对于STL的迭代器,以及函数对象,pass-by-value比pass-by-reference-to-const的效率更高一些。
因此对于内置类型、STL迭代器、函数对象 这三类,使用by value方式传递参数更好。
4、是不是小型的type都可以pass-by-value?
不是的,从下述三个方面考虑。