缺省情況下,C++ 以傳值方式將對(duì)象傳入或傳出函數(shù)(這是一個(gè)從 C 繼承來(lái)的特性)。除非你特別指定其它方式,否則函數(shù)的參數(shù)就會(huì)以實(shí)際參數(shù)(actual argument)的拷貝進(jìn)行初始化,而函數(shù)的調(diào)用者會(huì)收到函數(shù)返回值的一個(gè)拷貝。這個(gè)拷貝由對(duì)象的拷貝構(gòu)造函數(shù)生成。這就使得傳值(pass-by-value)成為一個(gè)代價(jià)不菲的操作。例如,考慮下面這個(gè)類層級(jí)結(jié)構(gòu):
class Person {
public:
Person(); // parameters omitted for simplicity
virtual ~Person(); // see Item 7 for why this is virtual
...
private:
std::string name;
std::string address;
};
class Student: public Person {
public:
Student(); // parameters again omitted
~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
現(xiàn)在,考慮以下代碼,在此我們調(diào)用一個(gè)函數(shù)—— validateStudent,它得到一個(gè) Student 參數(shù)(以傳值的方式),并返回它是否驗(yàn)證有效的結(jié)果:
bool validateStudent(Student s); // function taking a Student
// by value
Student plato; // Plato studied under Socrates
bool platoIsOK = validateStudent(plato); // call the function
當(dāng)這個(gè)函數(shù)被調(diào)用時(shí)會(huì)發(fā)生什么呢?
很明顯,Student 的拷貝構(gòu)造函數(shù)被調(diào)用,用 plato 來(lái)初始化參數(shù) s。同樣明顯的是,當(dāng) validateStudent 返回時(shí),s 就會(huì)被銷毀。所以這個(gè)函數(shù)的參數(shù)傳遞代價(jià)是一次 Student 的拷貝構(gòu)造函數(shù)的調(diào)用和一次 Student 的析構(gòu)函數(shù)的調(diào)用。
但這還不是全部。一個(gè) Student 對(duì)象內(nèi)部包含兩個(gè) string 對(duì)象,所以每次你構(gòu)造一個(gè) Student 對(duì)象的時(shí)候,你也必須構(gòu)造兩個(gè) string 對(duì)象。一個(gè) Student 對(duì)象還要從一個(gè) Person 對(duì)象繼承,所以每次你構(gòu)造一個(gè) Student 對(duì)象的時(shí)候,你也必須構(gòu)造一個(gè) Person 對(duì)象。一個(gè) Person 對(duì)象內(nèi)部又包含兩個(gè)額外的 string 對(duì)象,所以每個(gè) Person 的構(gòu)造也承擔(dān)著另外兩個(gè) string 的構(gòu)造。最終,以傳值方式傳遞一個(gè) Student 對(duì)象的后果就是引起一次 Student 的拷貝構(gòu)造函數(shù)的調(diào)用,一次 Person 的拷貝構(gòu)造函數(shù)的調(diào)用,以及四次 string 的拷貝構(gòu)造函數(shù)調(diào)用。當(dāng) Student 對(duì)象的拷貝被銷毀時(shí),每一個(gè)構(gòu)造函數(shù)的調(diào)用都對(duì)應(yīng)一個(gè)析構(gòu)函數(shù)的調(diào)用,所以以傳值方式傳遞一個(gè) Student 的全部代價(jià)是六個(gè)構(gòu)造函數(shù)和六個(gè)析構(gòu)函數(shù)!
好了,這是正確的和值得的行為。畢竟,你希望你的全部對(duì)象都得到可靠的初始化和銷毀。盡管如此,如果有一種辦法可以繞過(guò)所有這些構(gòu)造和析構(gòu)過(guò)程,應(yīng)該變得更好,這就是:傳引用給 const(pass by reference-to-const):
bool validateStudent(const Student& s);
這樣做非常有效:沒(méi)有任何構(gòu)造函數(shù)和析構(gòu)函數(shù)被調(diào)用,因?yàn)闆](méi)有新的對(duì)象被構(gòu)造。被修改的參數(shù)聲明中的 const 是非常重要的。 validateStudent 的最初版本接受一個(gè) Student 值參數(shù),所以調(diào)用者知道它們屏蔽了函數(shù)對(duì)它們傳入的 Student 的任何可能的改變;validateStudent 也只能改變它的一個(gè)拷貝?,F(xiàn)在 Student 以引用方式傳遞,同時(shí)將它聲明為 const 是必要的,否則調(diào)用者必然擔(dān)心 validateStudent 改變了它們傳入的 Student。
以傳引用方式傳遞參數(shù)還可以避免切斷問(wèn)題(slicing problem)。當(dāng)一個(gè)派生類對(duì)象作為一個(gè)基類對(duì)象被傳遞(傳值方式),基類的拷貝構(gòu)造函數(shù)被調(diào)用,而那些使得對(duì)象的行為像一個(gè)派生類對(duì)象的特殊特性被“切斷”了。你只剩下一個(gè)純粹的基類對(duì)象——這沒(méi)什么可吃驚的,因?yàn)槭且粋€(gè)基類的構(gòu)造函數(shù)創(chuàng)建了它。這幾乎絕不是你希望的。例如,假設(shè)你在一組實(shí)現(xiàn)一個(gè)圖形窗口系統(tǒng)的類上工作:
class Window {
public:
...
std::string name() const; // return name of window
virtual void display() const; // draw window and contents
};
class WindowWithScrollBars: public Window {
public:
...
virtual void display() const;
};
所有 Window 對(duì)象都有一個(gè)名字,你能通過(guò) name 函數(shù)得到它,而且所有的窗口都可以顯示,你可一個(gè)通過(guò)調(diào)用 display 函數(shù)來(lái)做到這一點(diǎn)。display 為 virtual 的事實(shí)清楚地告訴你:一個(gè)純粹的基類的 Window 對(duì)象的顯示方法有可能不同于專門的 WindowWithScrollBars 對(duì)象的顯示方法。
class Person {
public:
Person(); // parameters omitted for simplicity
virtual ~Person(); // see Item 7 for why this is virtual
...
private:
std::string name;
std::string address;
};
class Student: public Person {
public:
Student(); // parameters again omitted
~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
現(xiàn)在,考慮以下代碼,在此我們調(diào)用一個(gè)函數(shù)—— validateStudent,它得到一個(gè) Student 參數(shù)(以傳值的方式),并返回它是否驗(yàn)證有效的結(jié)果:
bool validateStudent(Student s); // function taking a Student
// by value
Student plato; // Plato studied under Socrates
bool platoIsOK = validateStudent(plato); // call the function
當(dāng)這個(gè)函數(shù)被調(diào)用時(shí)會(huì)發(fā)生什么呢?
很明顯,Student 的拷貝構(gòu)造函數(shù)被調(diào)用,用 plato 來(lái)初始化參數(shù) s。同樣明顯的是,當(dāng) validateStudent 返回時(shí),s 就會(huì)被銷毀。所以這個(gè)函數(shù)的參數(shù)傳遞代價(jià)是一次 Student 的拷貝構(gòu)造函數(shù)的調(diào)用和一次 Student 的析構(gòu)函數(shù)的調(diào)用。
但這還不是全部。一個(gè) Student 對(duì)象內(nèi)部包含兩個(gè) string 對(duì)象,所以每次你構(gòu)造一個(gè) Student 對(duì)象的時(shí)候,你也必須構(gòu)造兩個(gè) string 對(duì)象。一個(gè) Student 對(duì)象還要從一個(gè) Person 對(duì)象繼承,所以每次你構(gòu)造一個(gè) Student 對(duì)象的時(shí)候,你也必須構(gòu)造一個(gè) Person 對(duì)象。一個(gè) Person 對(duì)象內(nèi)部又包含兩個(gè)額外的 string 對(duì)象,所以每個(gè) Person 的構(gòu)造也承擔(dān)著另外兩個(gè) string 的構(gòu)造。最終,以傳值方式傳遞一個(gè) Student 對(duì)象的后果就是引起一次 Student 的拷貝構(gòu)造函數(shù)的調(diào)用,一次 Person 的拷貝構(gòu)造函數(shù)的調(diào)用,以及四次 string 的拷貝構(gòu)造函數(shù)調(diào)用。當(dāng) Student 對(duì)象的拷貝被銷毀時(shí),每一個(gè)構(gòu)造函數(shù)的調(diào)用都對(duì)應(yīng)一個(gè)析構(gòu)函數(shù)的調(diào)用,所以以傳值方式傳遞一個(gè) Student 的全部代價(jià)是六個(gè)構(gòu)造函數(shù)和六個(gè)析構(gòu)函數(shù)!
好了,這是正確的和值得的行為。畢竟,你希望你的全部對(duì)象都得到可靠的初始化和銷毀。盡管如此,如果有一種辦法可以繞過(guò)所有這些構(gòu)造和析構(gòu)過(guò)程,應(yīng)該變得更好,這就是:傳引用給 const(pass by reference-to-const):
bool validateStudent(const Student& s);
這樣做非常有效:沒(méi)有任何構(gòu)造函數(shù)和析構(gòu)函數(shù)被調(diào)用,因?yàn)闆](méi)有新的對(duì)象被構(gòu)造。被修改的參數(shù)聲明中的 const 是非常重要的。 validateStudent 的最初版本接受一個(gè) Student 值參數(shù),所以調(diào)用者知道它們屏蔽了函數(shù)對(duì)它們傳入的 Student 的任何可能的改變;validateStudent 也只能改變它的一個(gè)拷貝?,F(xiàn)在 Student 以引用方式傳遞,同時(shí)將它聲明為 const 是必要的,否則調(diào)用者必然擔(dān)心 validateStudent 改變了它們傳入的 Student。
以傳引用方式傳遞參數(shù)還可以避免切斷問(wèn)題(slicing problem)。當(dāng)一個(gè)派生類對(duì)象作為一個(gè)基類對(duì)象被傳遞(傳值方式),基類的拷貝構(gòu)造函數(shù)被調(diào)用,而那些使得對(duì)象的行為像一個(gè)派生類對(duì)象的特殊特性被“切斷”了。你只剩下一個(gè)純粹的基類對(duì)象——這沒(méi)什么可吃驚的,因?yàn)槭且粋€(gè)基類的構(gòu)造函數(shù)創(chuàng)建了它。這幾乎絕不是你希望的。例如,假設(shè)你在一組實(shí)現(xiàn)一個(gè)圖形窗口系統(tǒng)的類上工作:
class Window {
public:
...
std::string name() const; // return name of window
virtual void display() const; // draw window and contents
};
class WindowWithScrollBars: public Window {
public:
...
virtual void display() const;
};
所有 Window 對(duì)象都有一個(gè)名字,你能通過(guò) name 函數(shù)得到它,而且所有的窗口都可以顯示,你可一個(gè)通過(guò)調(diào)用 display 函數(shù)來(lái)做到這一點(diǎn)。display 為 virtual 的事實(shí)清楚地告訴你:一個(gè)純粹的基類的 Window 對(duì)象的顯示方法有可能不同于專門的 WindowWithScrollBars 對(duì)象的顯示方法。