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

            Beginning to 編程

            VC++ 方面編程文章

             

            EffectiveC++2ed 關(guān)于函數(shù)返回對象,引用還是指針

            我看到EffectiveC++2ed中函數(shù)返回對象中的說明感覺以后再也不想讓返回任何東西啦。比較怕。

            但是有的時候不返回任何東西是不行的阿。

            返回引用,返回指針,返回對象到底怎么寫?!

            ——————————————————————————————
            下面是EC中的內(nèi)容

            條款23: 必須返回一個對象時不要試圖返回一個引用

            據(jù)說愛因斯坦曾提出過這樣的建議:盡可能地讓事情簡單,但不要過于簡單。在c++語言中相似的說法應(yīng)該是:盡可能地使程序高效,但不要過于高效。

            一旦程序員抓住了“傳值”在效率上的把柄(參見條款22),他們會變得十分極端,恨不得挖出每一個隱藏在程序中的傳值操作。豈不知,在他們不懈地追求純粹的“傳引用”的過程中,他們會不可避免地犯另一個嚴(yán)重的錯誤:傳遞一個并不存在的對象的引用。這就不是好事了。

            看一個表示有理數(shù)的類,其中包含一個友元函數(shù),用于兩個有理數(shù)相乘:

            class rational {
            public:
            ? rational(int numerator = 0, int denominator = 1);

            ? ...

            private:
            ? int n, d;????????????? // 分子和分母

            friend
            ? const rational????????????????????? // 參見條款21:為什么
            ??? operator*(const rational& lhs,??? // 返回值是const
            ????????????? const rational& rhs)????
            };

            inline const rational operator*(const rational& lhs,
            ??????????????????????????????? const rational& rhs)
            {
            ? return rational(lhs.n * rhs.n, lhs.d * rhs.d);
            }

            很明顯,這個版本的operator*是通過傳值返回對象結(jié)果,如果不去考慮對象構(gòu)造和析構(gòu)時的開銷,你就是在逃避作為一個程序員的責(zé)任。另外一件很明顯的事實是,除非確實有必要,否則誰都不愿意承擔(dān)這樣一個臨時對象的開銷。那么,問題就歸結(jié)于:確實有必要嗎?

            答案是,如果能返回一個引用,當(dāng)然就沒有必要。但請記住,引用只是一個名字,一個其它某個已經(jīng)存在的對象的名字。無論何時看到一個引用的聲明,就要立即問自己:它的另一個名字是什么呢?因為它必然還有另外一個什么名字(見條款m1)。拿operator*來說,如果函數(shù)要返回一個引用,那它返回的必須是其它某個已經(jīng)存在的rational對象的引用,這個對象包含了兩個對象相乘的結(jié)果。

            但,期望在調(diào)用operator*之前有這樣一個對象存在是沒道理的。也就是說,如果有下面的代碼:

            rational a(1, 2);??????????????? // a = 1/2
            rational b(3, 5);??????????????? // b = 3/5
            rational c = a * b;????????????? // c 為 3/10

            期望已經(jīng)存在一個值為3/10的有理數(shù)是不現(xiàn)實的。如果operator* 一定要返回這樣一個數(shù)的引用,就必須自己創(chuàng)建這個數(shù)的對象。

            一個函數(shù)只能有兩種方法創(chuàng)建一個新對象:在堆棧里或在堆上。在堆棧里創(chuàng)建對象時伴隨著一個局部變量的定義,采用這種方法,就要這樣寫operator*:

            // 寫此函數(shù)的第一個錯誤方法
            inline const rational& operator*(const rational& lhs,
            ???????????????????????????????? const rational& rhs)
            {
            ? rational result(lhs.n * rhs.n, lhs.d * rhs.d);
            ? return result;
            }

            這個方法應(yīng)該被否決,因為我們的目標(biāo)是避免構(gòu)造函數(shù)被調(diào)用,但result必須要象其它對象一樣被構(gòu)造。另外,這個函數(shù)還有另外一個更嚴(yán)重的問題,它返回的是一個局部對象的引用,關(guān)于這個錯誤,條款31進(jìn)行了深入的討論。

            那么,在堆上創(chuàng)建一個對象然后返回它的引用呢?基于堆的對象是通過使用new產(chǎn)生的,所以應(yīng)該這樣寫operator*:

            // 寫此函數(shù)的第二個錯誤方法
            inline const rational& operator*(const rational& lhs,
            ???????????????????????????????? const rational& rhs)
            {
            ? rational *result =
            ??? new rational(lhs.n * rhs.n, lhs.d * rhs.d);
            ? return *result;
            }

            首先,你還是得負(fù)擔(dān)構(gòu)造函數(shù)調(diào)用的開銷,因為new分配的內(nèi)存是通過調(diào)用一個適當(dāng)?shù)臉?gòu)造函數(shù)來初始化的(見條款5和m8)。另外,還有一個問題:誰將負(fù)責(zé)用delete來刪除掉new生成的對象呢?

            實際上,這絕對是一個內(nèi)存泄漏。即使可以說服operator*的調(diào)用者去取函數(shù)返回值地址,然后用delete去刪除它(絕對不可能——條款31展示了這樣的代碼會是什么樣的),但一些復(fù)雜的表達(dá)式會產(chǎn)生沒有名字的臨時值,程序員是不可能得到的。例如:

            rational w, x, y, z;

            w = x * y * z;

            兩個對operator*的調(diào)用都產(chǎn)生了沒有名字的臨時值,程序員無法看到,因而無法刪除。(再次參見條款31)

            也許,你會想你比一般的熊——或一般的程序員——要聰明;也許,你注意到在堆棧和堆上創(chuàng)建對象的方法避免不了對構(gòu)造函數(shù)的調(diào)用;也許,你想起了我們最初的目標(biāo)是為了避免這種對構(gòu)造函數(shù)的調(diào)用;也許,你有個辦法可以只用一個構(gòu)造函數(shù)來搞掂一切;也許,你的眼前出現(xiàn)了這樣一段代碼:operator*返回一個“在函數(shù)內(nèi)部定義的靜態(tài)rational對象”的引用:

            // 寫此函數(shù)的第三個錯誤方法
            inline const rational& operator*(const rational& lhs,
            ???????????????????????????????? const rational& rhs)
            {
            ? static rational result;????? // 將要作為引用返回的
            ?????????????????????????????? // 靜態(tài)對象

            ? lhs和rhs 相乘,結(jié)果放進(jìn)result;

            ? return result;
            }

            這個方法看起來好象有戲,雖然在實際實現(xiàn)上面的偽代碼時你會發(fā)現(xiàn),不調(diào)用一個rational構(gòu)造函數(shù)是不可能給出result的正確值的,而避免這樣的調(diào)用正是我們要談?wù)摰闹黝}。就算你實現(xiàn)了上面的偽代碼,但,你再聰明也不能最終挽救這個不幸的設(shè)計。

            想知道為什么,看看下面這段寫得很合理的用戶代碼:

            bool operator==(const rational& lhs,????? // rationals的operator==
            ??????????????? const rational& rhs);???? //

            rational a, b, c, d;

            ...

            if ((a * b) == (c * d)) {

            ? 處理相等的情況;

            } else {

            ? 處理不相等的情況;

            }

            看出來了嗎?((a*b) == (c*d)) 會永遠(yuǎn)為true,不管a,b,c和d是什么值!

            用等價的函數(shù)形式重寫上面的相等判斷語句就很容易明白發(fā)生這一可惡行為的原因了:

            if (operator==(operator*(a, b), operator*(c, d)))

            注意當(dāng)operator==被調(diào)用時,總有兩個operator*剛被調(diào)用,每個調(diào)用返回operator*內(nèi)部的靜態(tài)rational對象的引用。于是,上面的語句實際上是請求operator==對“operator*內(nèi)部的靜態(tài)rational對象的值”和“operator*內(nèi)部的靜態(tài)rational對象的值”進(jìn)行比較,這樣的比較不相等才怪呢!

            幸運的話,我以上的說明應(yīng)該足以說服你:想“在象operator*這樣的函數(shù)里返回一個引用”實際上是在浪費時間。但我沒幼稚到會相信幸運總會光臨自己。一些人——你們知道這些人是指誰——此刻會在想,“唔,上面那個方法,如果一個靜態(tài)變量不夠用,也許可以用一個靜態(tài)數(shù)組……”

            請就此打住!我們難道還沒受夠嗎?

            我不能讓自己寫一段示例代碼來太高這個設(shè)計,因為即使只抱有上面這種想法都足以令人感到羞愧。首先,你必須選擇一個n,指定數(shù)組的大小。如果n太小,就會沒地方儲存函數(shù)返回值,這和我們前面否定的那個“采用單個靜態(tài)變量的設(shè)計”相比沒有什么改進(jìn)。如果n太大,就會降低程序的性能,因為函數(shù)第一次被調(diào)用時數(shù)組中每個對象都要被創(chuàng)建。這會帶來n個構(gòu)造函數(shù)和n個析構(gòu)函數(shù)的開銷,即使這個函數(shù)只被調(diào)用一次。如果說"optimization"(最優(yōu)化)是指提高軟件的性能的過程, 那么現(xiàn)在這種做法簡直可以稱為"pessimization"(最差化)。最后,想想怎么把需要的值放到數(shù)組的對象中以及需要多大的開銷?在對象間傳值的最直接的方法是通過賦值,但賦值的開銷又有多大呢?一般來說,它相當(dāng)于調(diào)用一個析構(gòu)函數(shù)(摧毀舊值)再加上調(diào)用一個構(gòu)造函數(shù)(拷貝新值)。但我們現(xiàn)在的目標(biāo)正是為了避免構(gòu)造和析構(gòu)的開銷啊!面對現(xiàn)實吧:這個方法也絕對不能選用。

            所以,寫一個必須返回一個新對象的函數(shù)的正確方法就是讓這個函數(shù)返回一個新對象。對于rational的operator*來說,這意味著要不就是下面的代碼(就是最初看到的那段代碼),要不就是本質(zhì)上和它等價的代碼:

            inline const rational operator*(const rational& lhs,
            ??????????????????????????????? const rational& rhs)
            {
            ? return rational(lhs.n * rhs.n, lhs.d * rhs.d);
            }

            的確,這會導(dǎo)致“operator*的返回值構(gòu)造和析構(gòu)時帶來的開銷”,但歸根結(jié)底它只是用小的代價換來正確的程序運行行為而已。況且,你所擔(dān)心的開銷還有可能永遠(yuǎn)不會出現(xiàn):和所有程序設(shè)計語言一樣,c++允許編譯器的設(shè)計者采用一些優(yōu)化措施來提高所生成的代碼的性能,所以,在有些場合,operator*的返回值會被安全地除去(見條款m20)。當(dāng)編譯器采用了這種優(yōu)化時(當(dāng)前大部分編譯器這么做),程序和以前一樣繼續(xù)工作,只不過是運行速度比你預(yù)計的要快而已。

            以上討論可以歸結(jié)為:當(dāng)需要在返回引用和返回對象間做決定時,你的職責(zé)是選擇可以完成正確功能的那個。至于怎么讓這個選擇所產(chǎn)生的代價盡可能的小,那是編譯器的生產(chǎn)商去想的事。

            posted on 2006-03-24 15:51 Beginning to 編程 閱讀(2933) 評論(0)  編輯 收藏 引用 所屬分類: 心得體會

            導(dǎo)航

            統(tǒng)計

            常用鏈接

            留言簿(4)

            隨筆分類

            隨筆檔案

            文章檔案

            相冊

            BlogDev

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            91精品国产91久久久久福利| 色天使久久综合网天天| 色诱久久久久综合网ywww| 国产亚洲精久久久久久无码| 国产精品久久国产精麻豆99网站| 99久久夜色精品国产网站| 人妻无码精品久久亚瑟影视| 国产精品一区二区久久精品| 性高湖久久久久久久久AAAAA| 亚洲国产欧美国产综合久久 | 久久精品视频91| 久久精品成人欧美大片| 亚洲精品高清久久| 久久天天躁狠狠躁夜夜avapp| 老司机午夜网站国内精品久久久久久久久 | 青青草国产成人久久91网| 久久久精品国产| 久久精品国产精品亜洲毛片| 久久久精品人妻一区二区三区四| 久久综合九色综合欧美就去吻| 久久综合综合久久狠狠狠97色88| 国产aⅴ激情无码久久| 亚洲国产成人久久笫一页| 久久久WWW成人免费毛片| 久久久久久a亚洲欧洲aⅴ| 日产精品99久久久久久| 久久久黄色大片| 午夜精品久久久久久久无码| 国内精品久久久久久久久电影网| 久久伊人精品青青草原高清| 久久精品国产99久久久| 国产精品免费看久久久 | 久久国产乱子伦精品免费强| 久久综合狠狠综合久久| 亚洲国产精品无码久久SM| 久久精品免费一区二区| 久久精品中文字幕一区| 国内精品伊人久久久久777| 亚洲国产精品久久久天堂 | 久久夜色精品国产亚洲| 狠狠色婷婷久久一区二区三区|