条款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?

不是的,从下述三个方面考虑。

(1)小型的type 可能在今后的维护中变大。
(2)小型的type 的copy构造函数可能代价依然很大。
(3)即使小型的type 的copy构造函数的代价不大,编译器对待自定义类型和内置类型的态度是截然不同的。例如:编译器往往拒绝把对象放进缓存器,而接受把内置类型放进缓存器,即使它们的底层描述是一样的。能放进缓存器,效率肯定就会高一点。
原文地址:https://www.cnblogs.com/lasnitch/p/12764176.html