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

            MyMSDN

            MyMSDN記錄開發新知道

            [轉]從C++的Return Value Optimization (RVO)到C#的value type

            先看一段簡單的C++代碼:

            Type get(int I){
                return Type(i);
            } 
            
            Type t = get(1); 

            這里, 我們從C++的基本語義看上去, 應該是Type(i) 調用一次拷貝構造函數, 在堆棧中生成一個臨時對象;然后,用該對象構造返回對象;然后對這個臨時對象調用析構函數;在調用者方, 用返回的臨時對象調用拷貝構造函數以初始化對象t, 返回對象的析構函數在這之后, 函數返回之前調用。

            所以, Type t = get(i); 應該有三個拷貝構造函數和兩個析構函數的調用.

            可是, 還有一種說法是, 編譯器可能會對這兩個臨時對象進行優化,最終的優化結果會是只有一次的構造函數。因為很明顯地可以看到, 這里我們其實只是要用一個整數構造一個Type對象。

            嗯. 似乎很有道理!

            那么, 哪一種說法對呢? 沒有調查就沒有發言權,于是本人用VC++6.0做了實驗。 放了些cout<<…..在拷貝構造函數里,觀察打印的結果, 結果卻是跟我的simple, na?ve的預測一致。三個拷貝構造函數, 兩個析構函數。

            “你個弱智編譯器!腦袋進水了吧?”(忘了編譯器沒腦袋了)“很明顯在這個例子里我的兩個臨時對象都沒有用的啊!”

            于是,上網, 查資料, google一下吧!

            下面是我查到的一些結果:

            其實, 這種對值傳遞的優化的研究, 并不只局限于返回值。對下面這個例子:

            void f(T t) { } 
            void main(void){ 
                T t1;
                f(t1); 
            } 

            也有這種考慮。

            f(T)是按值傳遞的。語義上應該做一個復制, 使得函數內部對T的改變不會影響到原來的t1.

            但是,因為在調用f(t1)之后, 我們沒有再使用t1(除了一個隱含的destructor調用),是否可能把復制優化掉, 直接使用t1呢?這樣可以節省掉一個拷貝構造函數和一個析構函數。

            可是, 不論是對返回值的優化, 還是對上面這種局部對象的優化,在1995年的C++新標準草案出臺前都是為標準所嚴格限制的 (雖然有些編譯器并沒有遵行這個標準, 還是支持了這種“優化”)

            那么, 這又是為什么呢?

            這里面涉及到一個普遍的對side-effect的擔憂。

            什么又是side-effect呢?

            所謂side-effect就是一個函數的調用與否能夠對系統的狀態造成區別。

            int add(int i, int j){ return i+j; }就是沒有side-effect的,而

            void set(int* p, int I, int v){ p[I]=v; }就是有side-effect的。因為它改變了一個數組元素的值, 而這個數組元素在函數外是可見的。

            通常意義上來說, 所有的優化應該在不影響程序的可觀察行為的基礎上進行的。否則,快則快了, 結果卻和所想要的完全不同!

            而C++的拷貝構造函數和析構函數又很多都是有side-effect的。如果我們的“優化”去掉了一個有side-effect的拷貝構造函數和一個析構函數, 這個“優化”就有可能改變程序的可觀察行為。(注意, 我這里說的是“可能”,因為“負負得正”, 兩個有side-effect的函數的調用, 在不考慮并行運行的情況下, 也許反而不會影響程序的可觀察行為。不過, 這種塞翁失馬的事兒, 編譯器就很難判斷了)

            基于這種憂慮, 1995年以前的標準, 明確禁止對含有side-effect的拷貝構造函數和析構函數的優化。同時, 還有一些對C++擴充的提議, 考慮讓程序員自己對類進行允許優化的聲明。 程序員可以明確地告訴編譯器:不錯, 我這個拷貝構造函數, 析構函數是有side-effect, 但你別管, 盡管優化, 出了事有我呢!

            哎, side-effect真是一個讓人又恨又愛的東西!它使編譯器的優化變得困難;加大了程序維護和調試的難度。因此 functional language 把side-effect當作洪水猛獸一樣,干脆禁止。但同時,我們又很難離開side-effect. 不說程序員們更習慣于imperative 的編程方法, 象數據庫操作,IO操作都天然就是side-effect.

            不過,個人還是認為C++標準對“優化”的保守態度是有道理的。無論如何,讓“優化”可以潛在地偷偷地改變程序的行為總是讓人想起來就不舒服的。

            但是, 矛盾是對立統一的。(想當年俺馬列可得了八十多分呢)。 對這種aggressive的“優化”的呼聲是一浪高過一浪。 以Stan Lippeman為首的一小撮頑固分子對標準的顛覆和和平演變的陰謀從來就沒有停止過。 這不?在1996年的一個風雨交加的夜晚, 一個陰險的C++新標準草案出爐了。在這個草案里, 加入了一個名為RVO (Return Value Optimization) 的放寬對優化的限制, 妄圖走資本主義道路, 給資本家張目的提案。其具體內容就是說:允許編譯器對命名過的局部對象的返回進行優化, 即使拷貝構造函數/析構函數有side-effect也在所不惜。這個提議背后所隱藏的思想就是:為了提高效率, 寧可冒改變程序行為的風險。寧要資本主義的苗, 不要社會主義的草了!

            我想, 這樣的一個罪大惡極的提案竟會被提交,應該是因為C++的值拷貝的語義的效率實在太“媽媽的”了。 當你寫一個 Complex operator+(const Complex& c1, const Complex& c2);的時候, 竟需要調用好幾次拷貝構造函數和析構函數!同志們!(沉痛地, 語重心長地)社會主義的生產關系的優越性怎么體現啊?

            接下來, 當我想Google C++最新的標準, 看RVO是否被最終采納時, 卻什么也找不到了。 到ANSI的網站上去, 居然要付錢才能DOWNLOAD文檔。 “老子在城里下館子都不付錢, down你幾個爛文檔還要給錢?!”

            故事沒有結局, 實在是不爽。 也不知是不是因為標準還沒有敲定, 所以VC++6 就沒有優化, 還是VC根本就沒完全遵守標準。

            不過,有一點是肯定的。 當寫程序的時候, 最好不要依賴于RVO (有人, 象Stan Lippeman, 又叫它NRV優化)。 因為, 不論對標準的爭論是否已經有了結果, 實際上各個編譯器的實現仍還是各自為政, 沒有統一。 一個叫SCOtt Meyers的家伙(忘了是賣什么的了)就說, 如果你的程序依賴于RVO, 最好去掉這種依賴。也就是說, 不管RVO到底標準不標準, 你還是不能用。 不僅不能用, 還得時刻警惕著RVO可能帶來的程序行為上的變化。 (也不知這幫家伙瞎忙了半天到底為啥!)

            說到這里, 倒想起了C#里一個困惑了我很久的問題。記得讀C#的specification的時候, 非常不解為什么C#不允許給value type 定義析構函數。

            這里, 先簡略介紹一下C#里的value type (原始數據類型, struct 類型)。

            在C#里的value_type就象是值, 永遠只能copy, 取值。因此, 它永遠是in-place的。如果你把一個value type的數據放在一個對象里,它的生命期就和那個對象相同;如果你聲明一個value type 的變量在函數中, 它的生命期就在lexical scope里。

            {

              The_ValueType value;

            }//value 到這里就死菜了

            啊呀呀! 這不正是我們懷念的C++的stack object嗎?

            在C++里,Auto_ptr, shared_ptr, 容器們, 不都是利用析構函數來管理資源的嗎?

            C#,Java 雖然利用garbage collection技術來收集無用對象, 使我們不用再擔心內存的回收。 但garbage collection并不保證無用對象一定被收集, 并不保證Dispose()函數一定被調用, 更不保證一個對象什么時候被回收。 所以對一些非內存的資源, 象數據庫連接, 網絡連接, 我們還是希望能有一個類似于smart pointer的東西來幫我們管理啊。(try-finally 雖然可以用, 但因為它影響到lexical scope, 有時用起來不那么方便)

            于是, 我對C#的取消value type的析構函數充滿了深厚的階級仇恨。

            不過, 現在想來, C#的這種設計一定是懲于C++失敗的教訓:

               1. value type 沒有拷貝構造函數。C#只做缺省copy, 沒有side-effect
               2. value type 不準有析構函數。C#有garbage collection, 析構函數的唯一用途只會是做一些side-effect象關閉數據庫連接。 所以取消了析構函數, 就取消了value type的side-effect.
               3. 沒有了side-effect, 系統可以任意地做優化了

            對以下程序:

            The_Valuetype get(int I){return The_Valuetype(i);}

            The_Valuetype t = get(1);

            在C#里我們可以快樂地說:只調用了一次構造函數。 再沒有side-effect的沙漠, 再沒有難以優化的荒原, smart pointer望而卻步, 效率之花處處開遍。 I have a dream, ……

            轉載自:http://gugu99.itpub.net/post/34143/466008

            posted on 2010-04-06 19:42 volnet 閱讀(574) 評論(0)  編輯 收藏 引用 所屬分類: 知識庫(KnowledgeLibrary)C/C++

            特殊功能
             
            伊人久久大香线蕉av不变影院| 久久精品亚洲精品国产欧美| 亚洲Av无码国产情品久久| 国内精品久久久久久久亚洲| 久久无码AV中文出轨人妻| 久久丫忘忧草产品| 国产精品久久久久影院色 | 四虎亚洲国产成人久久精品| 精品久久久无码人妻中文字幕| 久久精品国产精品青草app| 超级97碰碰碰碰久久久久最新| 国内精品伊人久久久久av一坑| 久久精品亚洲精品国产欧美| 久久99精品国产麻豆| 午夜视频久久久久一区| 91久久精品电影| 欧美激情精品久久久久| 日本五月天婷久久网站| 国内精品伊人久久久久网站| 久久婷婷五月综合97色| 久久国产AVJUST麻豆| 久久精品中文字幕有码| 国产精品无码久久四虎| 久久久久久久综合日本亚洲| 久久国产色AV免费看| 亚洲国产另类久久久精品小说| 精品久久人人妻人人做精品| 成人久久久观看免费毛片| 亚洲精品无码久久久久| 伊人久久大香线蕉综合Av| 久久精品免费全国观看国产| 久久影院亚洲一区| 中文字幕久久精品| 久久久免费观成人影院 | 人妻无码精品久久亚瑟影视| 久久99精品国产麻豆不卡| 国产毛片久久久久久国产毛片| 日本免费一区二区久久人人澡| 久久久久久久99精品免费观看| 久久精品国产99国产精偷| 久久中文字幕一区二区|