优势1 效率高
假设现在有一个Person类作为基类,还有一个Student类继承自Person,如下:
class Person {
public:
Person();
virtual ~Person(); // 最好把基类的析构函数加上virtual
private:
std::string name;
std::string address;
};
class Student : public Person {
public:
Student();
~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
现在我们有一个函数,叫validateStudent,需要传入一个Student对象,并返回它是否有效。
bool validateStudent(Student s);
Student plato;
bool platoIsOK = validateStudent(plato);
那么在这个函数调用的过程中,会发生什么事情呢?首先,Student的拷贝构造函数会被调用,以plato为蓝本将s进行初始化。同样,函数执行结束后,这个创建的s会被销毁。因此,这次参数传递的成本是"一次Student拷贝构造函数的调用,加上一次Student析构函数的调用"。然而,这只是其中的一部分,Student对象内还有两个string对象,所以,每次构造Student对象也就构造了这两个string对象。此外Student对象继承自Person对象,每次构造Student对象也必须构造出一个Person对象。一个Person对象又有两个string对象在里面。这么看的话,通过传值的方式传递一个Student对象,总的成本是"六次构造函数和六次析构函数"(包含了string对象的构造和析构)。
如果是通过传递引用常量的方式,则函数声明应该这样写:
bool validateStudent(const Student& s);
这种传递方式的效率要高得多,因为在这个过程中,没有任何构造函数和析构函数的调用,因为没有任何新的对象创建。这里的const是必须的,因为调用者会忧虑validate会不会改变他们传入的那个Student,这么做可以有效地保护传入的对象。
优势2 避免slicing(切割)问题
先来看看下面这个Window类:
class Window {
public:
...
std::string name() const; // 返回窗口名称
virtual void display() const; // 显示窗口和其内容
};
class WindowWithScrollBars : public Window {
public:
...
virtual void display() const;
}
所有Window对象都带有一个名称,你可以通过name函数取得它。所有窗口都可以显示,你可以通过display函数完成它。display是一个virtual函数,这意味着简易朴素的base class Window对象的显示方式和华丽高贵的WindowWithScrollBars对象的显示方式不同。
现在假设你希望写个函数打印窗口名称,然后显示该窗口。下面是错误示范:
void printNameAndDisplay(Window w) { // 不正确!参数可能被切割
std::cout << w.name();
w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
参数w会构造一个Window对象, 它是pass by value的。这就会导致wwsb里面包含的所有WindowWithScrollBars的内容都会被切除。对于printNameAndDisplay函数来说,不论传过来的是什么对象,参数w就是一个Window对象,其内部也会调用Window::display。
解决切割(slicing)问题的办法,就是以reference-to-const的方式传递w。则修改后的printNameAndDisplay如下:
void printNameAndDisplay(const Window& w) {
std::cout << w.name();
w.display();
}
如果窥视C++编译器的底层,你会发现,reference往往以指针实现出来,因此pass by reference通常意味着真正传递的是指针。因此如果你有个对象属于内置类型(例如int), pass by value往往比pass by reference效率高些。对内置类型而言,当你有机会采用pass-by-value或pass-by-reference-to-const时,选择pass-by-value并非没有道理。这个忠告也适用于STL的迭代器和函数对象,因为习惯上它们都被设计为passed by value。