(一)
調用函數的時候如果傳遞參數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);
當上述函數調用validateStudent(plato);時,Student的copy構造函數被調用,以plato為藍本將s初始化。當validateStudent返回s被銷毀。所以參數的傳遞成本是“一次Student copy構造函數調用,一次Student析構函數調用”。
但是還不是整個故事,Student對象內有兩個string對象,Student對象繼承自Person對象,因此必須構造出Person對象,Person對象又有兩個string在其中。
因此以by-value方式傳遞一個Student對象會導致調用一次Student copy構造函數、一次Person copy構造函數、四次string copy構造函數。當那個Student復件被銷毀,每個構造函數動作多需要一個對應的析構函數調用動作。所以總體成本是“六次構造函數和六次析構函數”!
解決方法: pass-by-reference-to-const
bool validateStudent(const Student& s);這種方式沒有任何構造和析構函數被調用,因為沒有任何新對象被創建。const是必要的,調用者不用擔心validateStudent改變他們傳入的那個Student。
(二)
by reference傳遞參數還可以避免slicing(對象切割)問題。當一個derived class對象以by value傳遞給一個base class對象。base class的copy構造函數被調用,而“構造此對象的行為像個derived class對象”的那些特化性質被切割掉了,僅留下一個base class對象。因為正是base class的構造函數創建了它。
假設有下面的繼承關系:
class Window { public: string name() const; virtual void display() const; }; class WindowWithScrollBars : public Window { public: virtual void display() const; };錯誤的函數寫法:
void printNameAndDisplay(Window w) { std::cout << w.name(); w.display(); }當調用上面這個函數的時候:
WindowWithScrollBars wwsb; printNameAndDisplay(wwsb);w會被構造成一個Window對象:他是pass-by-value,造成wwsb“之所以是個WindowWithScrollBars對象”的所有特化信息被切除。所以display調用的總是Window::display()。這肯定不是我們想要的!!!
解決方法:
以by reference to const的方式傳遞w:
void printNameAndDisplay(const Window& w) { cout << w.name(); w.display(); }現在,傳進來的窗口是什麼類型,w就表現出那種類型。
(三)
某些編譯器對待“內置類型”和“用戶自定義類型”截然不同,縱使兩者有相同的底層表述。有些編譯器會把一個double變量放進緩存器,但是不會把只含有一個double的class放進緩存器。
窺視c++編譯器的底層,你會發現,reference往往以指針實現出來,因此pass-by-reference通常意味著傳遞的是指針。因此如果你有個對象屬於內置類型(例如int)pass by value往往比pass by reference的效率要高些。這個忠告也適用於stl的迭代器和函數對象,因此它們習慣上被設計為pass by value。實踐者有責任看看它們是否高效且不受切割問題的影響。