C++ 请以pass-by-reference-to-const替换pass-by-value
20180312 C++ 請以pass-by-reference-to-const替換pass-by-value
缺省情況下C++以by value方式(一個繼承自C的方式)傳遞對象至(或來自)函數,除非你另外指定,否則函數參數就是以實際實參的復件(副本)為初值,而調用端所獲得的亦是函數返回值的一個復件。這些復件(副本)是由對象的copy構造函數產出,這可能使得pass-by-value成為昂貴的(費時的)操作。考慮以下class繼承體系:
class Person{
public:
? Person(); ? ? ? ? //為求簡化,省略參數
? virtual ~Person();
? ...
private:
? std::string name;
? std::string address;
};
class Student: public Person{
public:
? Student();
? ~Student();
? ...
private:
? std::string schoolName;
? std:;string schoolAddress;
};
現在考慮以下代碼,其中調用函數validateStudent,后者需要一個Student實參(by value)并返回它是否有效:
bool validateStudent(Student s);//函數以by value方式接受學生
Student yanhui;//顏回,孔子的學生
bool yanhuiisOK = validateStudent(yanhui);//調用函數
上述函數被調用時,無疑的,Student的copy構造函數會被調用,以yanhui為藍本將顏回初始化。同樣明顯地,當validateStudent返回s會被銷毀。因此,對于函數而言,參數的傳遞成本是“一次Student 拷貝構造函數調用,加上一次Student析構函數被調用”。
詳細過程為:Student對象內有兩個string對象,所以每次構造一個Student對象也就構造了兩個string對象,此Student對象繼承自Person對象,所以每次構造Student對象也必須構造出一個Person對象。一個Person對象又有兩個string對象在其中,因此每一次Person構造動作又需承擔兩個string構造動作。最終結果是,以by value方式傳遞一個Student對象會導致調用一次Student 拷貝構造函數,一次Person拷貝構造函數,四次string拷貝構造函數。當函數內的那個Student復件被銷毀,每一個構造函數調用動作都需要一個對應的析構函數調用動作。因此,以by value方式傳遞一個Student對象,總體成本是“六次構造函數和六次析構函數”。
如果想出一個方法可以回避所有構造和析構函數就好了。有的!就是pass by reference-to-const:
bool validateStudent(const Student& s);
現在Student以by reference方式傳遞,將它聲明為const是必要的,因為不這樣做的話調用者會憂慮validateStudent會不會改變他們傳入的那個Student。
以by reference方式傳遞參數也可以避免對象切割(slicing)問題。假設在一組classes上工作,用來實現一個圖形窗口系統:
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函數,這意味著簡易樸素的基類Window對象的顯示方式和華麗WindowWithScrollBars對象的顯示方式不同。(派生類是基類的擴展)。
現在假設寫個函數打印窗口名稱,然后顯示窗口,下面的示范是錯誤的:
void printNameAndDisplay(Window w)//不正確! 參數可能被切割
{
? std::cout<<w.name();
? w.display();
}
當調用上述函數并交給他WindowWithScrollingBars對象 會怎樣呢?
WindowWithScrollingBars wwsb;
printNameAndDiaplay(wwsb);
參數w會被構造成一個Window對象;它是pass by value,而造成wwsb“是個WindowWithScrollingBars對象”的所有特化信息都會被切除。在printNameAndDisplay函數內不論傳遞過來的對象原本是什么類型,參數w就像一個Window對象(因為其類型的Window)。因此在printNameAndDisplay內調用display掉用的總是Window::display,絕對不會是WindowWithScrollingBars::diaplay。
解決切割(slicing)問題的辦法,就是以by reference-to-const的方式傳遞w:
void printNameAndDisplay(const Window& w)//很好,參數不會被切割
{
? std::cout<<w.name;
? w.dispaly();
}
現在,傳進來的窗口是什么類型,w就表現出那種類型。
在C++底層實現中,reference往往以指針實現,因此pass by reference通常意味著真正傳遞的是指針,因此若對象屬于內置類型,pass by value往往比pass by reference效率更高,這種情況也適用于STL的迭代器和函數對象。
注意:
1、盡量以pass-by-reference-to-const替換pass-by-value。前者通常比較高效,并可以避免切割問題(slicing problem)。
2、以上規則并不適用于內置類型,以及STL的迭代器和函數對象。對它們而言,pass-by-value往往比較合適。
總結
以上是生活随笔為你收集整理的C++ 请以pass-by-reference-to-const替换pass-by-value的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黑苹果hidp显示不清楚_macOS 2
- 下一篇: [C++学习] effective c+