優勢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。