以pass-by-reference-to-const替換pass-by-value

缺省情況下C++以by value方式傳遞對象至(或來自)函數。除非你另外指定,否則函數參數都是以實際實參的復件(副本)為初值,而調用端所獲得的亦是函數返回的一個復件,這些復件(副本)由對象的copy構造函數產出,這可能使得pass-by-value成為昂貴的(費時的)操作。

好處1:高效,避免副本對象的創建

考慮以下的繼承體系:

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實參(by value)並返回它是否有效
Student plato;
bool IsOK = validateStudent(plato); //調用函數

在調用函數時,Student的copy構造函數會被調用,以plato為藍本將s初始化,在函數返回時,s又會被銷燬。因此對此函數而言,參數的傳遞成本是“一次Student Copy構造函數調用,加上一次Student析構函數調用”。注意,Student對象內有兩個string對象,所以每次構造一個Student對象也就構造了兩個string對象。此外,Student對象繼承自Person對象,所以每次構造Student對象也必須構造出一個Person對象,Person對象又有兩個string對象在其中。。。因此以by-value方式傳遞一個Student對象,總體成本是“六次構造函數和六次析構函數”!

如果傳遞對象的引用,高效的多:

bool validateStudent(const Student& s);

沒有新的對象被創建,const也會保證不對s做任何修改。

好處2:避免對象切割問題

當一個derived class對象以value方式傳遞並視為一個base class對象,base class的copy構造函數會被調用,derived class對象的特性會被全部切割掉

class Person{
public:
    ...
    string name() const;
};
class Student:public Person{
public:
    ...
    string name() const;
};

調用:

void show(Person p){
    cout<<p.name();
} 

Student s;
show(s);

此時調用的是Person::name()。解決切割問題的方法是以引用傳遞參數:

void show(const Person& p)

注意:內置類型,STL的迭代器和函數對象,往往pass-by-value比較適當。

必須返回對象時,別妄想返回其reference

考慮有理數Rational,有個友元操作符*,返回Rational對象。

Rational operator*( const Rational &lhs, const Rational &rhs ) {
    Rational result = ...
    return result; 
}

返回對象,導致臨時對象的構造,析構。效率低,因此會想返回方法內局部對象的引用,這種方法不可行,為什麼?

1. 定義一個局部變量,就是在stack空間創建對象,方法執行完畢,局部對象銷燬。假如返回方法內局部對象的引用,方法執行完,局部對象銷燬,這時候,引用指向一堆垃圾。

2.或者在heap上構造一個對象,返回引用。這也不可行,首先,要求客户端負責delete,這不合理。其次,退一步説,就算客户負責,執行delete。但是有些情況,客户無法執行delete。比如:Rational d = a*b*c; a*b的結果沒有暴露出來,客户無法執行delete。

3. 既然方法內的局部對象會銷燬,你可能會想返回一個reference指向方法內的local static對象,但是這會導致下面的詭異情況:無論何時,a*b == c*d 總是為真。為什麼?

local static對象只初始化一次,a*b修改local static對象的值,並返回其引用,c*d也修改local static對象的值,並返回其引用。它們是同一個對象的別名,當然永遠相等。只不過,後一次的修改覆蓋了前一個修改。

因此,對於這種情況,就讓方法返回對象。為了正確的行為,必須付出一定代價。需要説明的是,通過RVO技術,編譯器可以進行優化,避免這種代價。

結論:絕不要返回pointer或reference指向一個local stack對象,返回reference指向一個heap-allocated對象,返回pointer或reference指向一個local static對象而可能同時需要多個這樣的對象。