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

            大龍的博客

            常用鏈接

            統(tǒng)計

            最新評論

            C++中的臨時變量

            C++中的臨時變量

            它們是被神所遺棄的孩子,沒有人見過它們,更沒有人知道它們的名字.它們命中注定徘徊于命運邊緣高聳的懸崖和幽深的深淵之間,
            用自己短暫的生命撫平了生與死之間的縫隙.譬如朝露,卻與陽光無緣.是該為它們立一座豐碑的時候了,墓銘志上寫著:我來了,我走了,我快樂過.
            許多人對臨時變量的理解僅僅限于:
            string temp;
            其實,從C++的觀點來看,這根本就不是臨時變量,而是局部變量.

            C++的臨時變量是編譯器在需要的時候自動生成的臨時性變量,它們并不在代碼中出現(xiàn).但是它們在編譯器生成的二進制編碼中是存在的,
            也創(chuàng)建和銷毀.在C++語言中,臨時變量的問題格外的重要,因為每個用戶自定義類型的臨時變量都要出發(fā)用戶自定義的構(gòu)造函數(shù)和析構(gòu)函數(shù)(如果用戶提供了)

            又是該死的編譯器!又該有人抱怨編譯器總在自己背后干著偷偷摸摸的事情了.但是如果離開了編譯器的這些工作,我們可能寸步難行.

            如果X是一個用戶自定義的類型,有默認(rèn)構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),賦值運算函數(shù),析構(gòu)函數(shù)(這也是類的4個基本函數(shù)),那么請考慮以下代碼:
            X get(X arg)
            {
               return arg;
            }

            X a;
            X b = get(a);
            即使是這么簡單的代碼也是很難實現(xiàn)的
            讓我們分析一下代碼執(zhí)行過程中發(fā)生了什么?
            首先我要告訴你一個秘密:對于一個函數(shù)來說,無論是傳入一個對象還是傳出一個對象其實都是不可能的.
            讓一個函數(shù)傳入或傳出一個內(nèi)置的數(shù)據(jù)類型,例如int,是很容易的,但是對于用戶自定義類型得對象卻非常的困難,因為編譯器總得找地方為這些對象
            寫上構(gòu)造函數(shù)和析構(gòu)函數(shù),不是在函數(shù)內(nèi),就是在函數(shù)外,除非你用指針或引用跳過這些困難

            那么怎么辦?在這里,編譯器必須玩一些必要的小花招,嗯,其中的關(guān)鍵恰恰就是臨時變量

            對于以對象為形參的函數(shù):
            void foo(X x0)
            {
            }
            X xx;
            foo(xx);
            編譯器一般按照以下兩種轉(zhuǎn)換方式中的一種進行轉(zhuǎn)換
            1.在函數(shù)外提供臨時變量
            void foo(X& x0)    //修改foo的聲明為引用
            {
            }
            X xx;         //聲明xx
            X::X(xx);       //調(diào)用xx的默認(rèn)構(gòu)造函數(shù)
            X __temp0;      //聲明臨時變量__temp0
            X::X(__temp0, xx); //調(diào)用__temp0的拷貝構(gòu)造函數(shù)
            foo(__temp0);     //調(diào)用foo
            X::~X(__temp0);    //調(diào)用__temp0的析構(gòu)函數(shù)
            X::~X(xx);      //調(diào)用xx的析構(gòu)函數(shù)
            2.在函數(shù)內(nèi)提供臨時變量
            void foo(X& x0)    //修改foo的聲明為引用
            {
               X __temp0;      //聲明臨時變量__temp0
               X::X(__temp0, x0); //調(diào)用__temp0的拷貝構(gòu)造函數(shù)
               X::~X(__temp0);    //調(diào)用__temp0的析構(gòu)函數(shù)
            }
            X xx;         //聲明xx
            X::X(xx);       //調(diào)用xx的默認(rèn)構(gòu)造函數(shù)
            foo(xx);       //調(diào)用foo
            X::~X(xx);      //調(diào)用xx的析構(gòu)函數(shù)

            無論是在函數(shù)的內(nèi)部聲明臨時變量還是在函數(shù)的外部聲明臨時變量,其實都是差不多的,這里的含義是說既然參數(shù)要以傳值的
            語意傳入函數(shù),也就是實參xx其實并不能修改,那么我們就用一個一摸一樣臨時變量來移花接木,完成這個傳值的語意
            但是這樣做也不是沒有代價,編譯器要修改函數(shù)的聲明,把對象改為對象的引用,同時修改所有函數(shù)調(diào)用的地方,代價確實巨大啊,
            但是這只是編譯器不高興而已,程序員和程序執(zhí)行效率卻沒有影響
            對于以對象為返回值的函數(shù):
            X foo()
            {
               X xx;
               return xx;
            }

            X yy = foo();
            編譯器一般按照以下方式進行轉(zhuǎn)換
            void foo(X& __temp0) //修改foo的聲明為引用
            {
               X xx;         //聲明xx
               X::X(xx);       //調(diào)用xx的默認(rèn)構(gòu)造函數(shù)
               __temp0::X::X(xx); //調(diào)用__temp0的拷貝構(gòu)造函數(shù)
               X::~X(xx);      //調(diào)用xx的析構(gòu)函數(shù)
            }

            X yy;          //聲明yy
            X __temp0;       //聲明臨時變量__temp0
            foo(__temp0);      //調(diào)用foo
            X::X(yy, __temp0);   //調(diào)用yy的拷貝構(gòu)造函數(shù)
            X::~X(__temp0);     //調(diào)用__temp0的析構(gòu)函數(shù)
            X::~X(yy);       //調(diào)用yy的析構(gòu)函數(shù)

            既然我們已經(jīng)聲明了yy,為什么還要緊接著聲明__temp0,其實這里完全可以把yy和臨時變量合一
            優(yōu)化后,上面的代碼看起來象這個樣子:
            void foo(X& __temp0) //修改foo的聲明為引用
            {
               X xx;         //聲明xx
               X::X(xx);       //調(diào)用xx的默認(rèn)構(gòu)造函數(shù)
               __temp0::X::X(xx); //調(diào)用__temp0的拷貝構(gòu)造函數(shù)
               X::~X(xx);      //調(diào)用xx的析構(gòu)函數(shù)
            }

            X yy;          //聲明yy
            foo(yy);        //調(diào)用foo
            X::~X(yy);       //調(diào)用yy的析構(gòu)函數(shù)

            嗯,怎么說呢,這算是一種優(yōu)化算法吧,其實這各個技巧已經(jīng)非常普遍了,并擁有一個專門的名稱Named Return Value(NRV)優(yōu)化
            NRV優(yōu)化如今被視為標(biāo)準(zhǔn)C++編譯器的一個義不容辭的優(yōu)化操作(雖然其需求其實超出了正式標(biāo)準(zhǔn)之外)
            除了以類為參數(shù)以外,如果參數(shù)的類型是const T&類型,這也可能導(dǎo)致臨時變量
            void fun(const string& str)
            const char* name = "wgs";
            fun(name);
            嗯,還記得在const文檔中的論述嗎?對于這種特殊的參數(shù)類型,編譯器是很樂意為你做自動轉(zhuǎn)換的工作的,代價嘛,就是一個臨時變量,
            不過如果是你自己去做,大概就只能聲明一個局部變量了

            為什么函數(shù)和臨時變量這么有緣,其實根本的原因在于對象傳值的語意,這一個也是為什么C++中鼓勵傳對象地址的原因

            和函數(shù)的情況類似的,還有一大類情況是臨時變量的樂土,那就是表達式
            string s,t;
            printf("%s", s + t);
            這里s+t的結(jié)果該放在什么地方呢?只能是臨時變量中.

            這個printf語句帶來了新的問題,那就是"臨時變量的生命期"是如何的?
            對于函數(shù)的情況,我們已經(jīng)看到了,臨時變量在完成交換內(nèi)容的使命后都是盡量早的被析構(gòu)了,那么對于表達式呢?
            如果在s+t計算后析構(gòu),那么print函數(shù)打印的就是一個非法內(nèi)容了,因此C++給出的規(guī)則是:
            臨時變量應(yīng)該在導(dǎo)致臨時變量創(chuàng)建的"完整表達式"求值過程的最后一個步驟被析構(gòu)
            什么又是"完整表達式"?簡單的說,就是不是表達式的子表達式
            這條規(guī)則聽起來很簡單,但具體實現(xiàn)起來就非常的麻煩了,例如:
            X foo(int n)
            if (foo(1) || foo(2) || foo(3) )
            其中X中有operator int()轉(zhuǎn)換,所以可以用在if語句中
            這里的foo(1)將產(chǎn)生一個臨時變量1,如果這部分為false,foo(2)將繼續(xù)產(chǎn)生一個臨時變量,如果這部分也為false,foo(3)...
            一個臨時變量的參數(shù)居然是和運行時相關(guān)的,更要命的是你要記住你到底產(chǎn)生了幾個臨時變量并在這個表達式結(jié)束的時候進行析構(gòu)以小心的維護對象構(gòu)造和析構(gòu)的一致
            我猜想,這里會展開成一段復(fù)雜的代碼,并加入更多的if判斷才能搞定,呵呵,好在我不是做編譯器的

            上面的規(guī)則其實還有兩條例外:
            string s,t;
            string v = 1 ? s + t : s - t;
            這里完整表達式是?語句,但是在完整表達式結(jié)束以后臨時變量還不能立即銷毀,而必須在變量v賦值完成后才能銷毀,這就是例外規(guī)則1:
            凡含有表達式執(zhí)行結(jié)果的臨時變量,應(yīng)該存留到對象的初始化操作完成后銷毀

            string s,t;
            string& v = s + t;
            這里s+t產(chǎn)生的臨時變量即使在變量v的賦值完成后也不能銷毀,否則這個引用就沒用了,這就是例外規(guī)則2:
            如果一個臨時變量被綁定到一個引用,這個臨時變量應(yīng)該留到這個臨時變量和這個引用那個先超出變量的作用域后才銷毀

            這篇文章可能有些深奧了,畢竟大多數(shù)內(nèi)容來自于<<Inside The C++ Object Model>>
            那么就留下一條忠告:
            在stl中,以下的代碼是錯誤的
            string getName();
            char* pTemp = getName().c_str();
            getName返回的就是一個臨時變量,在把它內(nèi)部的char指針賦值給pTemp后析構(gòu)了,這時pTemp就是一個非法地址
            確實如C++發(fā)明者Bjarne Stroustrup所說,這種情況一般發(fā)生在不同類型的相互轉(zhuǎn)換上

            在Qt中,類似的代碼是這樣的
            QString getName();
            char* pTemp = getName().toAscii().data();
            這時pTemp是非法地址

            希望大家不要犯類似的錯誤

            posted on 2009-09-25 12:57 大龍 閱讀(627) 評論(0)  編輯 收藏 引用


            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            奇米综合四色77777久久| 99精品国产在热久久无毒不卡| 久久无码av三级| 久久香蕉国产线看观看猫咪?v| 日产精品久久久久久久| 大伊人青草狠狠久久| 三级片免费观看久久| 精品多毛少妇人妻AV免费久久| 蜜臀av性久久久久蜜臀aⅴ麻豆| 久久亚洲精品中文字幕三区| 色婷婷综合久久久久中文字幕| 久久w5ww成w人免费| 麻豆国内精品久久久久久| 久久久久久无码Av成人影院| 久久夜色精品国产亚洲av| 久久发布国产伦子伦精品| 久久人人爽人人爽人人片av麻烦| 国内精品久久久久久久97牛牛| 久久婷婷五月综合成人D啪| 国产∨亚洲V天堂无码久久久| 亚洲欧美国产日韩综合久久| 伊人丁香狠狠色综合久久| 亚洲国产精品高清久久久| 久久人人爽人人爽人人片AV麻豆 | 精品久久一区二区三区| 久久免费看黄a级毛片| 久久综合亚洲色HEZYO国产| 94久久国产乱子伦精品免费| 久久国产精品成人免费| 久久99精品久久久久久动态图| 东方aⅴ免费观看久久av| 伊人色综合久久天天人守人婷| 日韩亚洲国产综合久久久| 国产精品美女久久久网AV| 99久久精品无码一区二区毛片 | 精品国产乱码久久久久久1区2区 | 欧美午夜精品久久久久免费视| 欧美亚洲国产精品久久| 久久精品国产欧美日韩99热| 热99RE久久精品这里都是精品免费 | 亚洲中文字幕无码久久2017|