• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            洛譯小筑

            別來(lái)無(wú)恙,我的老友…
            隨筆 - 45, 文章 - 0, 評(píng)論 - 172, 引用 - 0
            數(shù)據(jù)加載中……

            [ECPP讀書(shū)筆記 條目20] 傳參時(shí)要多用“引用常量”,少用傳值

            默認(rèn)情況下,C++為函數(shù)傳入和傳出對(duì)象是采用傳值方式的(這是由C語(yǔ)言繼承而來(lái)的特征)。除非你明確使用其他方法,函數(shù)的形式參數(shù)總會(huì)通過(guò)復(fù)制實(shí)在參數(shù)的副本來(lái)創(chuàng)建,并且,函數(shù)的調(diào)用者得到的也是函數(shù)返回值的一個(gè)副本。這些副本是由對(duì)象的拷貝構(gòu)造函數(shù)創(chuàng)建的。這使得“傳值”成為一項(xiàng)代價(jià)十分昂貴的操作。請(qǐng)觀察下邊的示例中類(lèi)的層次結(jié)構(gòu):

            class Person {

            public:

              Person();                        // 省略參數(shù)表以簡(jiǎn)化代碼

              virtual ~Person();               // 條目7解釋了它為什么是虛函數(shù)

              ...

             

            private:

              std::string name;

              std::string address;

            };

             

            class Student: public Person {

            public:

              Student();                       // 再次省略參數(shù)表

              virtual ~Student();

              ...

             

            private:

              std::string schoolName;

              std::string schoolAddress;

            };

            請(qǐng)觀察下面的代碼,這里我們調(diào)用一個(gè)名為validateStudent的函數(shù),通過(guò)為這一函數(shù)傳進(jìn)一個(gè)Student類(lèi)型的參數(shù)(傳值方式),它將返回這一學(xué)生的身份是否合法:

            bool validateStudent(Student s);         // 通過(guò)傳值方式接受一個(gè)Student對(duì)象

             

            Student plato;                           // 柏拉圖是蘇格拉底的學(xué)生

             

             

            bool platoIsOK = validateStudent(plato); // 調(diào)用這一函數(shù)

            在這個(gè)函數(shù)被調(diào)用時(shí)將會(huì)發(fā)生些什么呢?

            很顯然地,在這一時(shí)刻,通過(guò)調(diào)用Student的拷貝構(gòu)造函數(shù),可以將這一函數(shù)的s參數(shù)初始化為plato的值。同樣顯然的是,svalidateStudent返回的時(shí)候?qū)⒈讳N(xiāo)毀。所以這一函數(shù)中傳參的開(kāi)銷(xiāo)就是調(diào)用一次Student的拷貝構(gòu)造函數(shù)和一次Student的析構(gòu)函數(shù)。

            但是上邊的分析僅僅是冰山一角。一個(gè)Student對(duì)象包含兩個(gè)string對(duì)象,所以每當(dāng)你構(gòu)造一個(gè)Student對(duì)象時(shí),你都必須構(gòu)造兩個(gè)string對(duì)象。同時(shí),由于Student類(lèi)是從Person類(lèi)繼承而來(lái),所以在每次構(gòu)造Student對(duì)象時(shí),你都必須再構(gòu)造一個(gè)Person對(duì)象。一個(gè)Person對(duì)象又包含兩個(gè)額外的string對(duì)象,所以每次對(duì)Person的構(gòu)造還要進(jìn)行額外的兩次string的構(gòu)造。最后的結(jié)果是,通過(guò)傳值方式傳遞一個(gè)Student對(duì)象會(huì)引入以下幾個(gè)操作:調(diào)用一次Student的拷貝構(gòu)造函數(shù),調(diào)用一次Person的拷貝構(gòu)造函數(shù),調(diào)用四次string的拷貝構(gòu)造函數(shù)。在Student的這一副本被銷(xiāo)毀時(shí),相應(yīng)的每次構(gòu)造函數(shù)調(diào)用都對(duì)應(yīng)著一次析構(gòu)函數(shù)的調(diào)用。因此我們看到:通過(guò)傳值方式傳遞一個(gè)Student對(duì)象總體的開(kāi)銷(xiāo)究竟有多大?竟達(dá)到了六次構(gòu)造函數(shù)和六次析構(gòu)函數(shù)的調(diào)用!

            下面向你介紹正確的方法,這一方法才會(huì)使函數(shù)擁有期望的行為。畢竟你期望的是所有對(duì)象以可靠的方式進(jìn)行初始化和銷(xiāo)毀。與此同時(shí),如果可以繞過(guò)所有這些構(gòu)造和析構(gòu)操作將是件很愜意的事情。這個(gè)方法就是:通過(guò)引用常量傳遞參數(shù):

            bool validateStudent(const Student& s);

            這樣做效率會(huì)提高很多:由于不會(huì)創(chuàng)建新的對(duì)象,所以就不會(huì)存在構(gòu)造函數(shù)或析構(gòu)函數(shù)的調(diào)用。改進(jìn)的參數(shù)表中的const是十分重要的。由于早先版本的validateStudent通過(guò)傳值方式接收Student參數(shù),所以調(diào)用者了解:無(wú)論函數(shù)對(duì)于傳入的Student對(duì)象進(jìn)行什么樣的操作,都不會(huì)對(duì)原對(duì)象造成任何影響,validateStudent僅僅會(huì)對(duì)對(duì)象的副本進(jìn)行修改。而改進(jìn)版本中Student對(duì)象是以引用形式傳入的,有必要將其聲明為const的,因?yàn)槿绻贿@樣,調(diào)用者就需要關(guān)心傳入validateStudentStudent對(duì)象有可能會(huì)被修改。

            通過(guò)引用傳參也可以避免“截?cái)鄦?wèn)題”。當(dāng)一個(gè)派生類(lèi)的對(duì)象以一個(gè)基類(lèi)對(duì)象的形式傳遞(傳值方式)時(shí),基類(lèi)的拷貝構(gòu)造函數(shù)就會(huì)被調(diào)用,此時(shí),這一對(duì)象的獨(dú)有特征——使它區(qū)別于基類(lèi)對(duì)象的特征會(huì)被“截掉”。剩下的只是一個(gè)簡(jiǎn)單的基類(lèi)對(duì)象,這并不奇怪,因?yàn)樗怯苫?lèi)構(gòu)造函數(shù)創(chuàng)建的。這肯定不是你想要的。請(qǐng)看下邊的示例,假設(shè)你正在使用一組類(lèi)來(lái)實(shí)現(xiàn)一個(gè)圖形窗口系統(tǒng):

            class Window {

            public:

              ...

              std::string name() const;        // 返回窗口的名字

              virtual void display() const;    // 繪制窗口和內(nèi)容

            };

             

            class WindowWithScrollBars: public Window {

            public:

              ...

              virtual void display() const;

            };

            所有的Window對(duì)象都有一個(gè)名字,可以通過(guò)name函數(shù)取得。所有的窗口都可以被顯示出來(lái),可以通過(guò)調(diào)用display實(shí)現(xiàn)。display是虛函數(shù),這一事實(shí)告訴我們,簡(jiǎn)單基類(lèi)Window的對(duì)象與派生出的WindowWithScrollBars對(duì)象的顯示方式是不一樣的。(參見(jiàn)條目3436

            現(xiàn)在,假設(shè)你期望編寫(xiě)一個(gè)函數(shù)來(lái)打印出當(dāng)前窗口的名字然后顯示這一窗口。下面是錯(cuò)誤的實(shí)現(xiàn)方法:

            void printNameAndDisplay(Window w) // 錯(cuò)誤! 參數(shù)傳遞的對(duì)象將被截?cái)啵?/span>

            {

              std::cout << w.name();

              w.display();

            }

            考慮一下當(dāng)你將一個(gè)WindowWithScrollBars對(duì)象傳入這個(gè)函數(shù)時(shí)將會(huì)發(fā)生些什么:

            WindowWithScrollBars wwsb;

             

            printNameAndDisplay(wwsb);

            參數(shù)w將被構(gòu)造為一個(gè)Window對(duì)象——還記得么?它是通過(guò)傳值方式傳入的。這里,使wwsb具體化的獨(dú)有信息將被截掉。無(wú)論傳入函數(shù)的對(duì)象的具體類(lèi)型是什么,在printNameAndDisplay的內(nèi)部,w將總保有一個(gè)Window類(lèi)的對(duì)象的身份(因?yàn)樗旧砭褪且粋€(gè)Window的對(duì)象)。特別地,在printNameAndDisplay內(nèi)部對(duì)display的調(diào)用總是Window::display,而永遠(yuǎn)不會(huì)是WindowWithScrollBars::display

            解決截?cái)鄦?wèn)題的方法是:通過(guò)引用常量傳參:

            void printNameAndDisplay(const Window& w)

            {                                  // 工作正常,參數(shù)將不會(huì)被截?cái)唷?/span>

              std::cout << w.name();

              w.display();

            }

            現(xiàn)在w的類(lèi)型就是傳入窗口對(duì)象的精確類(lèi)型。

            揭開(kāi)C++編譯器的面紗,你將會(huì)發(fā)現(xiàn)引用通常情況下是以指針的形式實(shí)現(xiàn)的,所以通過(guò)引用傳遞通常意味著實(shí)際上是在傳遞一個(gè)指針。因此,如果傳遞一個(gè)內(nèi)建數(shù)據(jù)類(lèi)型的對(duì)象(比如int),傳值會(huì)被傳遞引用更為高效。那么,對(duì)于內(nèi)建數(shù)據(jù)類(lèi)型,當(dāng)你在傳值和傳遞常量引用之間徘徊時(shí),傳值方式不失為一個(gè)更好的選擇。迭代器和STL中的函數(shù)對(duì)象也是如此,這是因?yàn)樗鼈冊(cè)O(shè)計(jì)的初衷就是能夠更適于傳值,這是C++的慣例。迭代器和函數(shù)對(duì)象的設(shè)計(jì)人員有責(zé)任考慮復(fù)制時(shí)的效率問(wèn)題和截?cái)鄦?wèn)題。(這也是一個(gè)“使用哪種規(guī)則,取決于當(dāng)前使用哪一部份的C++”的例子,參見(jiàn)條目1

            內(nèi)建數(shù)據(jù)類(lèi)型體積較小,所以一些人得出這樣的結(jié)論:所有體積較小的類(lèi)型都適合使用傳值,即使它們是用戶(hù)自定義的。這是一個(gè)不可靠的推理。僅僅通過(guò)一個(gè)對(duì)象體積小并不能判定調(diào)用它的拷貝構(gòu)造函數(shù)的代價(jià)就很低。許多對(duì)象——包括大多數(shù)STL容器——其中僅僅包含一個(gè)指針和很少量的其它內(nèi)容,但是復(fù)制此類(lèi)對(duì)象的同時(shí),它所指向的所有內(nèi)容都需要復(fù)制。這將付出十分高昂的代價(jià)。

            即使體積較小的對(duì)象的拷貝構(gòu)造函數(shù)不會(huì)帶來(lái)巨大的開(kāi)銷(xiāo),它也會(huì)引入性能問(wèn)題。一些編譯器對(duì)內(nèi)建數(shù)據(jù)類(lèi)型和用戶(hù)自定義數(shù)據(jù)類(lèi)型是分別對(duì)待的,即使它們的表示方式完全相同。比如說(shuō)一些編譯器很樂(lè)意將一個(gè)單純的double值放入寄存器中,這是語(yǔ)言的常規(guī);但將一個(gè)僅包含一個(gè)double值的對(duì)象放入寄存器時(shí),編譯器就會(huì)報(bào)錯(cuò)了。當(dāng)你遇到這種事情時(shí),你可以使用引用傳遞這類(lèi)對(duì)象,因?yàn)榫幾g器此時(shí)一定會(huì)將指針(引用的具體實(shí)現(xiàn))放入寄存器中。

            對(duì)于“小型的用戶(hù)自定義數(shù)據(jù)類(lèi)型不適用于傳值方式”還有一個(gè)理由,那就是:作為用戶(hù)自定義類(lèi)型,它們的大小可能會(huì)改變。現(xiàn)在很小的類(lèi)型在未來(lái)的版本中可能會(huì)變得很大,這是因?yàn)樗膬?nèi)部實(shí)現(xiàn)方式可能會(huì)改變。即使是你更改了C++語(yǔ)言的具體實(shí)現(xiàn)都可能會(huì)影響到類(lèi)型的大小。比如,在我編寫(xiě)上面的示例的時(shí)候,一些對(duì)標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)中string的大小竟然達(dá)到了另一些的七倍。

            總體上講,只有內(nèi)建數(shù)據(jù)類(lèi)型、STL迭代器和函數(shù)對(duì)象類(lèi)型適用于傳值方式。對(duì)于所有其它的類(lèi)型,都應(yīng)該遵循本條款中的建議:盡量使用引用常量傳參,而不是傳值。

            時(shí)刻牢記

            盡量使用引用常量傳參,而不是傳值方式。因?yàn)橐话闱闆r下傳引用更高效,而且可以避免“截?cái)鄦?wèn)題”。

            對(duì)于內(nèi)建數(shù)據(jù)類(lèi)型、STL迭代和函數(shù)對(duì)象類(lèi)型,這一規(guī)則就不適用了,對(duì)它們來(lái)說(shuō)通常傳值方式更實(shí)用。

            posted on 2007-06-01 18:12 ★ROY★ 閱讀(1445) 評(píng)論(3)  編輯 收藏 引用 所屬分類(lèi): Effective C++

            評(píng)論

            # re: 【翻譯】[Effective C++第三版?中文版][第20條]盡量使用“引用常量”傳參,而不是傳值  回復(fù)  更多評(píng)論   

            好漂亮的程序啊!
            2007-06-02 09:16 | 深藍(lán)色的音符

            # re: 【翻譯】[Effective C++第三版?中文版][第20條]盡量使用“引用常量”傳參,而不是傳值  回復(fù)  更多評(píng)論   

            給你做了個(gè)鏈接,希望以后能跟你多多交流.
            因?yàn)槲椰F(xiàn)在也在開(kāi)始學(xué)習(xí)C++,不過(guò)好難啊!
            2007-06-02 13:04 | 深藍(lán)色的音符

            # re: 【翻譯】[Effective C++第三版?中文版][第20條]盡量使用“引用常量”傳參,而不是傳值  回復(fù)  更多評(píng)論   

            說(shuō)的很對(duì)
            2007-06-04 14:02 | picasa
            色综合久久综精品| 99久久99久久精品国产片| 亚洲国产日韩欧美久久| 久久久久99这里有精品10| 奇米影视7777久久精品| 日本三级久久网| 97精品国产97久久久久久免费| 99久久精品国产高清一区二区 | 伊人久久大香线蕉无码麻豆| 少妇精品久久久一区二区三区| 中文字幕一区二区三区久久网站| 中文精品99久久国产 | 精品国产VA久久久久久久冰| 久久亚洲中文字幕精品一区| 色偷偷偷久久伊人大杳蕉| 欧美精品福利视频一区二区三区久久久精品| 国产色综合久久无码有码| 免费一级欧美大片久久网| 精品久久久久中文字幕日本| 欧美亚洲国产精品久久高清| 国产精品美女久久久久AV福利| 精品国产乱码久久久久久郑州公司 | 久久99精品久久久久久hb无码| 少妇人妻综合久久中文字幕| 精品免费久久久久国产一区| 99久久超碰中文字幕伊人| 浪潮AV色综合久久天堂| 久久狠狠爱亚洲综合影院| 亚洲伊人久久综合中文成人网| 国产免费福利体检区久久| 国产AV影片久久久久久| 99久久综合国产精品二区| 国产69精品久久久久777| 国产成人久久激情91| 久久精品国产精品亚洲精品| 99久久综合狠狠综合久久止| 韩国三级大全久久网站| 久久99精品综合国产首页| 88久久精品无码一区二区毛片| 国产成人精品综合久久久| 久久久久综合中文字幕|