• <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>

            洛譯小筑

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

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

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

            class Person {

            public:

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

              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;

            };

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

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

             

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

             

             

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

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

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

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

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

            bool validateStudent(const Student& s);

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

            通過引用傳參也可以避免“截?cái)鄦栴}”。當(dāng)一個(gè)派生類的對象以一個(gè)基類對象的形式傳遞(傳值方式)時(shí),基類的拷貝構(gòu)造函數(shù)就會被調(diào)用,此時(shí),這一對象的獨(dú)有特征——使它區(qū)別于基類對象的特征會被“截掉”。剩下的只是一個(gè)簡單的基類對象,這并不奇怪,因?yàn)樗怯苫悩?gòu)造函數(shù)創(chuàng)建的。這肯定不是你想要的。請看下邊的示例,假設(shè)你正在使用一組類來實(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對象都有一個(gè)名字,可以通過name函數(shù)取得。所有的窗口都可以被顯示出來,可以通過調(diào)用display實(shí)現(xiàn)。display是虛函數(shù),這一事實(shí)告訴我們,簡單基類Window的對象與派生出的WindowWithScrollBars對象的顯示方式是不一樣的。(參見條目3436

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

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

            {

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

              w.display();

            }

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

            WindowWithScrollBars wwsb;

             

            printNameAndDisplay(wwsb);

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

            解決截?cái)鄦栴}的方法是:通過引用常量傳參:

            void printNameAndDisplay(const Window& w)

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

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

              w.display();

            }

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

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

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

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

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

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

            時(shí)刻牢記

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

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

            posted on 2007-06-01 18:12 ★ROY★ 閱讀(1425) 評論(3)  編輯 收藏 引用 所屬分類: Effective C++

            評論

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

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

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

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

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

            說的很對
            2007-06-04 14:02 | picasa
            精品久久久久久中文字幕大豆网| 色综合久久88色综合天天 | 国内精品久久久久| 久久久久亚洲精品男人的天堂| 久久久久亚洲精品日久生情 | 久久国产精品一区二区| 久久久噜噜噜久久中文字幕色伊伊| 久久精品国产亚洲AV香蕉| 91久久精品电影| 新狼窝色AV性久久久久久| 久久精品国产欧美日韩| 国产成年无码久久久久毛片| 超级碰久久免费公开视频| 国产精品久久久久久吹潮| 中文字幕精品久久| 久久久久亚洲精品中文字幕| 99精品久久精品| 婷婷久久久亚洲欧洲日产国码AV| 久久播电影网| 久久精品国产99国产精偷| 久久久久无码精品国产不卡| 漂亮人妻被中出中文字幕久久 | 国产精品嫩草影院久久| 97热久久免费频精品99| 伊人久久大香线蕉综合Av| 2020久久精品亚洲热综合一本| 久久久久久国产精品免费免费| 久久免费视频网站| 久久精品国产一区| 婷婷久久综合九色综合98| 国产精品久久永久免费| 国产麻豆精品久久一二三| 激情伊人五月天久久综合| 久久精品国产亚洲AV无码偷窥| 浪潮AV色综合久久天堂| 久久久精品人妻一区二区三区蜜桃| 人妻少妇精品久久| 久久久久人妻一区二区三区 | 久久久无码精品亚洲日韩按摩| 久久久久久国产精品美女| 国内精品久久久久久久久电影网|