程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 讀書筆記 effctive c++ Item 20 優先使用按const-引用傳遞(by-reference-to-const)而不是按值傳遞(by value)

讀書筆記 effctive c++ Item 20 優先使用按const-引用傳遞(by-reference-to-const)而不是按值傳遞(by value)

編輯:關於C++

讀書筆記 effctive c++ Item 20 優先使用按const-引用傳遞(by-reference-to-const)而不是按值傳遞(by value)。本站提示廣大學習愛好者:(讀書筆記 effctive c++ Item 20 優先使用按const-引用傳遞(by-reference-to-const)而不是按值傳遞(by value))文章只能為提供參考,不一定能成為您想要的結果。以下是讀書筆記 effctive c++ Item 20 優先使用按const-引用傳遞(by-reference-to-const)而不是按值傳遞(by value)正文


1. 按值傳遞參數會有效率問題

默認情況下,C++向函數傳入或者從函數傳出對象都是按值傳遞(pass by value)(從C繼承過來的典型特性)。除非你指定其他方式,函數參數會用實際參數值的拷貝進行初始化,函數調用者會獲得函數返回值的一份拷貝。這些拷貝由對象的拷貝構造函數生成。這使得按值傳遞(pass-by-value)變成一項昂貴的操作。舉個例子,考慮下面的類繼承體系(Item 7):

 1 class Person {
 2 
 3 public:
 4 
 5 Person(); // parameters omitted for simplicity
 6 
 7 virtual ~Person(); // see Item 7 for why this is virtual
 8 
 9 ...
10 
11 private:
12 
13 std::string name;
14 
15 std::string address;
16 
17 };
18 
19 class Student: public Person {
20 
21 public:
22 
23 Student(); // parameters again omitted
24 
25 virtual ~Student();
26 
27 ...
28 
29 private:
30 
31 std::string schoolName;
32 
33 std::string schoolAddress;
34 
35 };

 

現在考慮下面的代碼,在這裡我們調用了一個函數,validateStudent,這個函數有一個Student參數(按值),返回值表示驗證是否通過:

1 bool validateStudent(Student s); // function taking a Student
2 
3 // by value
4 
5 Student plato; // Plato studied under Socrates
6 
7 bool platoIsOK = validateStudent(plato); // call the function

 

當函數被調用時會發生什麼?

很清楚,Student拷貝構造函數會被調用,用plato來初始化參數s。同樣很清楚的是,當validateStudent函數返回後s會被銷毀。所以這個函數參數傳遞的開銷是分別調用了構造函數和析構函數。

但這不是所有的開銷。一個Student對象中有兩個string對象,所以每次你構建一個Student對象的時候你必須構造兩個string對象。Student對象繼承自Person對象,所以每次你構建一個Student對象你必須構造一個Person對象。一個Person對象中有兩個額外的string對象,所以每個Person構造函數同樣需要對兩個額外的string進行構造。最後結果是按值傳遞一個Student對象導致對Student拷貝構造函數的一次調用,對Person拷貝構造函數的一次調用,對stirng拷貝構造函數的四次調用。當Student對象的拷貝被釋放時,每個構造函數對應的析構函數要被調用,所以按值傳遞一個Student對象的總開銷是6次構造和6次析構!!

2. 按const引用傳遞會更高效

這是正確的並且令人滿意的行為。畢竟,你需要的是所有對象被可靠的初始化和銷毀。並且,如果有一種方法能夠繞過這些構造函數和析構函數就再好不過了。這種方法是存在的,就是:按const引用進行傳遞(pass by reference-to-const)。

1 bool validateStudent(const Student& s);

 

這種用法更具效率:沒有構造函數或者析構函數被調用,因為沒有新的對象被創建。在修訂後版本的參數聲明中,const是很重要的。validataStudent的原始版本有一個按值傳遞的Studetn參數,調用者會知道對被傳遞進去的Student參數的任何可能的修改都會被屏蔽掉;validateStudent只是在修改它的一份拷貝。現在Student被按照引用進行傳遞,將其聲明為const同樣是必須的,否則調用者就會為傳遞進去的參數是否被修改而擔心。

3. 按const引用傳遞能避免切片問題

按引用傳遞參數同樣避免了切片(slicing)問題。當一個派生類對象被當作一個基類對象被傳遞時(按值傳遞),基類的拷貝構造函數會被調用,“使對象的行為看起來像派生類對象“這個特定的特性被“切掉”了。留給你的只剩下一個基類對象,因為是一個基類的構造函數創建了它。這是你永遠不希望看到的。舉個例子,假設你正在一些類上進行工作,這些類實現了圖形化窗口系統:

 1 class Window {
 2 
 3 public:
 4 
 5 ...
 6 
 7 std::string name() const; // return name of window
 8 
 9 virtual void display() const; // draw window and contents
10 
11 };
12 
13 class WindowWithScrollBars: public Window {
14 
15 public:
16 
17 ...
18 
19 virtual void display() const;
20 
21 };

 

所有的窗口對象都有一個名字,你可以通過name函數來獲取它,並且所有的窗口都能被顯示出來,你可以通過觸發display函數來實現。Display函數為虛函數的事實告訴你基類Windows對象的顯示方式同WindowWithScrollBars對象的顯示方式是不同的(Item 34和Item 36)。

現在假設你實現了一個函數,先打印窗口的名字然後讓窗口顯示出來。下面是實現這樣一個函數的錯誤的方式:

1 void printNameAndDisplay(Window w) // incorrect! parameter
2 
3 { // may be sliced!
4 
5 std::cout << w.name();
6 
7 w.display();
8 
9 }

 

考慮當你使用一個WindowWithScrollBars對象作為參數調用這個函數會發生什麼:

1 WindowWithScrollBars wwsb;
2 
3 printNameAndDisplay(wwsb);

 

 參數w將會被構造,它是按值傳遞的,所以w作為一個Window對象,所有讓wwsb看起來像一個WIndowWithScrollBars對象的特定信息都會被切除。在printNameAndDispay內部,w的行為總是會像Window對象一樣(因為他是一個Window類的對象),而不管傳入函數的參數類型是什麼。特別的,在printNameAndDisplay內部對display的調用總是會調用Window::display,永遠不會調用WindowWithScrollBars::display。

解決切片問題的方法是將w按const引用傳遞進去(by reference-to-const):

1 void printNameAndDisplay(const Window& w) // fine, parameter won’t
2 
3 { // be sliced
4 
5 std::cout << w.name();
6 
7 w.display();
8 
9 }

 

現在w的行為會和傳入參數的實際類型一致了。

4. 什麼情況下按值傳遞是合理的

如果你偷看一下C++編譯器的底層,你將會發現引用是按照指針來進行實現的,所以按引用傳遞一些東西就意味著傳遞一個指針。因此,如果你有一個內建類型的對象(例如int)按值傳遞比按引用傳遞效率更高。對於內建類型來說,當你在按值傳遞和按引用傳遞之間進行選擇時,選擇按值傳遞是合理的。這對於STL中的迭代器和函數對象同樣適用,因為按照慣例,它們被設計成按值傳遞。迭代器和函數對象的設計者有責任留意下面兩個問題:高效的拷貝和不用忍受切片問題。(這是一個規則如何被改變的例子,取決於你使用C++的哪一部分 見 Item 1。)

5. 並不是對象小就應該按值傳遞

內建類型占用了很少的內存,所以一些人得出結論:所有這樣的小的類型都是按值傳遞的候選者,即使它們是用戶定義的類型。這個原因是靠不住的。因為一個對象占用內存少並不意味這調用它的拷貝構造函數不昂貴。許多對象——這些對象中的大多數STL容器——僅僅包含一個指針,但是拷貝這些對象會拷貝它們指向的所有東西。這可是非常昂貴的操作。

即使是當小對象的拷貝構造函數的調用開銷很小時,也會有性能問題。一些編譯器對於內建類型和用戶自定義類型有不同的對待方式,即使它們有相同的底層表示(underlying representation)。舉個例子,一些編譯器拒絕將只含有一個double數值的對象放入緩存中,卻很高興的為一個赤裸裸的double這麼做。當這類事情發生的時候,將這些對象按引用傳遞會更好,因為編譯器會將指針(引用的實現)放入緩存中。

另外一個小的用戶自定義類型不是按值傳遞的好的候選者的原因是,作為用戶自定義類型,它們的大小會發生變化。一個類型現在可能很小但是在將來的發布中可能會變的更大,因為它的內部實現可能發生變化。當你切換到一個不同的C++實現時事情也有可能發生變化。舉個例子,標准庫的string類型的一些實現比其他實現大6倍。

一般情況下,你能夠對“按值傳遞是不昂貴的”進行合理假設的唯一類型就是內建類型和STL迭代器以及函數對象。對於其它的任何類型,遵循這個條款的建議,優先使用按const引用傳遞而不是按值傳遞。

6. 總結
  • 優先使用按const-引用傳遞而不是按值傳遞。它更具效率並且能夠避免切片問題。
  • 這個規則不適用於內建類型,STL迭代器和函數對象類型。對於它們來說,按值傳遞通常是合適的。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved