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。

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