1. 引用傳遞與值傳遞的選擇
熟悉C++的人都知道,C++中函數參數的默認傳遞方式是值傳遞(pass-by-value),這種傳遞方式的好處是在函數內部使用的是實參的一個副本,在函數內部對其操作不會影響實參的值。但是我們也知道,對象的拷貝是會有時間和空間消耗的,而且如果對象所占空間很大的話,以值來傳遞參數很可能相當費時而極大程序的限制了程序的性能。C++提供了一種高效的對象傳遞方式:引用傳遞。
自定義類型使用值傳遞帶來的問題:
第一,傳遞對象的效率低。傳遞對象會調用對象的構造函數,造成時間和空間的浪費。特別如果對象所占的空間很大時,帶來的負面效果尤為明顯。
第二,會引起“對象切割”問題。當子類以被視為父類的對象進行值傳遞時,傳遞之后,那些屬於子類的特征化信息會丟失掉,這絕對不是想要的結果。可以看下面的例子:
class Window { public: ... std::string name() const; virtual void display() const; }; class WindowWithScrollBars : public Window { ... virtual void display() const; };
以值傳遞WindowWithScrollBars的對象,而參數的類型是Window類型:
void printNameAndDisplay(Windown w) { std::cout << w.name(); w.display(); } WindowWithScrollBars wwsb; printNameAndDisplay(wwsb);
在上面的代碼中,wwsb以值進行傳遞,傳遞之后對象變為Window類型,所以調用w.display()調用的函數是Window::display(),而不是期望的WindowWithScrollBars::display()。當使用const引用傳遞時會解決這一問題。
第三,即使對象的規模較小,也不就使用值傳遞的方式。因為某些編譯器對待自定義類型和內置類型的方式不同。例如:某些編譯器會把double類型的變量放進寄存器內,卻不會對只由一個double類型的成員構造的對象這么做,但是編譯器卻會把指針放進寄存器(reference是通過指針實現的)。
綜上
- 《Effective C++》中對選擇參數傳遞方式的總結:除了內置類型、STL的迭代器和函數對象(后兩者通常認為擁有高效的拷貝效率)可以被設計為值傳遞之外,其他任何類型都應該使用const引用傳參(pass-by-reference-to-const)。
2. 返回值的傳遞
引用傳遞確實能夠帶來一些效率、性能上的好處,但是也不能濫用。如果一心追求在任何時候都將使用引用傳遞,將會帶來一個致使的錯誤,那就是傳遞的引用指向了並不存在的對象。這一節介紹一個在什么情況下不適合返回引用。(示例來自Effective C++條款21)
一個有理數的類,聲明了一個friend函數計算兩個有理數的乘積:
class Rational { public: Rational(int numerator = 0, int denominator = 1); ... private: int n, d; friend const Rational operator* (const Rational& lhs, const Rational& rhs); };
這個乘積函數以值傳遞來返回對象,那么顯然它會有一些開銷。那么可不可以考慮用引用傳遞來返回結果呢?要用引用傳遞,就不能返回局部變量,因為函數返回時局部變量會銷毀,只能采用棧或堆中的對象。如下面的實現方式:
const Rational& operator* (const Rational& lhs, const Rational& rhs) { Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d); return *result; }
現在,確實實現了引用的方式返回參數,但是還是有一個“構造函數調用”的開銷。而且,還有一個更大的問題:new出來的對象誰負責delete。假如要求調用者每次都會適當的釋放內存。但是下面的代碼:
Rational w, x, y, z;
w = x * y * z;
在這個過程中,調用了兩次operator*,但是沒辦法拿到中間那次乘法返回的引用,也就不能正確進行delete,絕對導致了內存泄漏。但如果禁止這種嵌套調用,那么顯然這個乘法的實現是很糟糕的。
還有一種方式來返回引用,讓返回的引用指向一個被定義於函數內部的static Rational對象:
const Rational& operator* (const Rational& lhs, const Rational& rhs) { static Rational result; ... return result; }
眾所周知,用static對象都會帶來多線程安全的問題。但是這種實現方式還會帶來另外一個更大的漏洞。考慮如下代碼:
bool operator==(const Rational& lhs, const Rational& rhs); Rational a, b, c, d; if ((a * b) == (c * d)) { ... }
這種調用方式,會造成a*b永遠和c*d相等,if條件永遠為true。因為a*b和c*d返回的引用指向了同一個static對象。
或許還有有人嘗試用更多的方式來返回引用,但那一定會帶來更多的開銷。因此,正確的方式就是:在必須以值傳遞的方式返回對象時,直接以值傳遞的方式返回結果,而不要考慮返回其引用。