C++为什么要用传引用常量替换传值

优势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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章