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

            longshanks

              C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              14 Posts :: 0 Stories :: 214 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(10)

            我參與的團(tuán)隊(duì)

            搜索

            •  

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            C++的營養(yǎng)

            莫華楓
                動(dòng)物都會(huì)攝取食物,吸收其中的營養(yǎng),用于自身生長和活動(dòng)。然而,并非食物中所有的物質(zhì)都能為動(dòng)物所吸收。那些無法消化的物質(zhì),通過消化道的另一頭(某些動(dòng) 物消化道只有一頭)排出體外。不過,一種動(dòng)物無法消化的排泄物,是另一種動(dòng)物(生物)的食物,后者可以從中攝取所需的營養(yǎng)。
                一門編程語言,對(duì)于程序員而言,如同食物那樣,包含著所需的養(yǎng)分。當(dāng)然也包含著無法消化的東西。不同的是,隨著程序員不斷成長,會(huì)逐步消化過去無法消化的那些東西。
                C++可以看作一種成分復(fù)雜的食物,對(duì)于多數(shù)程序員而言,是無法完全消化的。正因?yàn)槿绱耍芏喑绦騿T認(rèn)為C++太難以消化,不應(yīng)該去吃它。但是,C++的 營養(yǎng)不可謂不豐富,就此舍棄,而不加利用,則是莫大的罪過。好在食物可以通過加工,變得易于吸收,比如說發(fā)酵。鑒于程序員們的消化能力的差異,也為了讓C ++的營養(yǎng)能夠造福他人,我就暫且扮演一回酵母菌,把C++的某些營養(yǎng)單獨(dú)提取出來,并加以分解,讓那些消化能力不太強(qiáng)的程序員也能享受它的美味。:)
                (為了讓這些營養(yǎng)便于消化,我將會(huì)用C#做一些案例。選擇C#的原因很簡(jiǎn)單,因?yàn)槲沂煜ぁ?))

            RAII

                RAII,好古怪的營養(yǎng)?。∷娜Q應(yīng)該是“Resource Acquire Is Initial”。這是C++創(chuàng)始人Bjarne Stroustrup發(fā)明的詞匯,比較令人費(fèi)解。說起來,RAII的含義倒也不算復(fù)雜。用白話說就是:在類的構(gòu)造函數(shù)中分配資源,在析構(gòu)函數(shù)中釋放資源。 這樣,當(dāng)一個(gè)對(duì)象創(chuàng)建的時(shí)候,構(gòu)造函數(shù)會(huì)自動(dòng)地被調(diào)用;而當(dāng)這個(gè)對(duì)象被釋放的時(shí)候,析構(gòu)函數(shù)也會(huì)被自動(dòng)調(diào)用。于是乎,一個(gè)對(duì)象的生命期結(jié)束后將會(huì)不再占用 資源,資源的使用是安全可靠的。
                下面便是在C++中實(shí)現(xiàn)RAII的典型代碼:
                    class file
                    {
                    
            public:
                        file(
            string const& name) {
                               m_fileHandle
            =open_file(name.cstr());
                        }
                        
            ~file() {
                               close_file(m_fileHandle);
                        }
                        ...
                    
            private:
                        handle m_fileHandle;
                    }
                很典型的“在構(gòu)造函數(shù)里獲取,在析構(gòu)函數(shù)里釋放”。如果我寫下代碼:       
                    void fun1() {
                        file myfile(
            "my.txt");
                        ... 
            //操作文件
                    }
                //此處銷毀對(duì)象,調(diào)用析構(gòu)函數(shù),釋放資源
                當(dāng)函數(shù)結(jié)束時(shí),局部對(duì)象myfile的生命周期也結(jié)束了,析構(gòu)函數(shù)便會(huì)被調(diào)用,資源會(huì)得到釋放。而且,如果函數(shù)中的代碼拋出異常,那么析構(gòu)函數(shù)也會(huì)被調(diào)用,資源同樣會(huì)得到釋放。所以,在RAII下,不僅僅資源安全,也是異常安全的。
                但是,在如下的代碼中,資源不是安全的,盡管我們實(shí)現(xiàn)了RAII:
                     void fun2() {
                         file pfile
            =new file("my.txt");
                            ... 
            //操作文件
                     }
                因?yàn)槲覀冊(cè)诙焉蟿?chuàng)建了一個(gè)對(duì)象(通過new),但是卻沒有釋放它。我們必須運(yùn)用delete操作符顯式地加以釋放:
                    void fun3() {
                         file pfile
            =new file("my.txt");
                            ... 
            //操作文件
                            delete pfile;
                    }
                否則,非但對(duì)象中的資源得不到釋放,連對(duì)象本身的內(nèi)存也得不到回收。(將來,C++的標(biāo)準(zhǔn)中將會(huì)引入GC(垃圾收集),但正如下面分析的那樣,GC依然無法確保資源的安全)。
                現(xiàn)在,在fun3(),資源是安全的,但卻不是異常安全的。因?yàn)橐坏┖瘮?shù)中拋出異常,那么delete pfile;這句代碼將沒有機(jī)會(huì)被執(zhí)行。C++領(lǐng)域的諸位大牛們告誡我們:如果想要在沒有GC的情況下確保資源安全和異常安全,那么請(qǐng)使用智能指針:
                    void fun4() {
                          shared_ptr
            <file> spfile(new file("my.txt"));
                          ... 
            //操作文件
                    }
              //此處,spfile結(jié)束生命周期的時(shí)候,會(huì)釋放(delete)對(duì)象
                那么,智能指針又是怎么做到的呢?下面的代碼告訴你其中的把戲(關(guān)于智能指針的更進(jìn)一步的內(nèi)容,請(qǐng)參考std::auto_ptr,boost或tr1的智能指針):
                    template<typename T>
                    
            class smart_ptr
                    
            {
                    
            public:
                        smart_ptr(T
            * p):m_ptr(p) {}
                        
            ~smart_ptr() { delete m_ptr; }
                        ...
                    
            private:
                        T
            * m_ptr;
                    }
                沒錯(cuò),還是RAII。也就是說,智能指針通過RAII來確保內(nèi)存資源的安全,也間接地使得對(duì)象上的RAII得到實(shí)施。不過,這里的RAII并不是十分嚴(yán) 格:對(duì)象(所占的內(nèi)存也是資源)的創(chuàng)建(資源獲取)是在構(gòu)造函數(shù)之外進(jìn)行的。廣義上,我們也把它劃歸RAII范疇。但是,Matthew Wilson在《Imperfect C++》一書中,將其獨(dú)立出來,稱其為RRID(Resource Release Is Destruction)。RRID的實(shí)施需要在類的開發(fā)者和使用者之間建立契約,采用相同的方法獲取和釋放資源。比如,如果在shared_ptr構(gòu)造 時(shí)使用malloc(),便會(huì)出現(xiàn)問題,因?yàn)閟hared_ptr是通過delete釋放對(duì)象的。
                對(duì)于內(nèi)置了GC的語言,資源管理相對(duì)簡(jiǎn)單。不過,事情并非總是這樣。下面的C#代碼摘自MSDN Library的C#編程指南,我略微改造了一下:
                    static void CodeWithoutCleanup()
                    
            {
                        System.IO.FileStream file 
            = null;
                        System.IO.FileInfo fileInfo 
            = new System.IO.FileInfo("C:\file.txt");
                        file 
            = fileInfo.OpenWrite();
                        file.WriteByte(
            0xF);
                    }
                那么資源會(huì)不會(huì)泄漏呢?這取決于對(duì)象的實(shí)現(xiàn)。如果通過OpenWrite()獲得的FileStream對(duì)象,在析構(gòu)函數(shù)中執(zhí)行了文件的釋放操作,那么資 源最終不會(huì)泄露。因?yàn)镚C最終在執(zhí)行GC操作的時(shí)候,會(huì)調(diào)用Finalize()函數(shù)(C#類的析構(gòu)函數(shù)會(huì)隱式地轉(zhuǎn)換成Finalize()函數(shù)的重 載)。這是由于C#使用了引用語義(嚴(yán)格地講,是對(duì)引用類型使用引用語義),一個(gè)對(duì)象實(shí)際上不是對(duì)象本身,而是對(duì)象的引用。如同C++中的那樣,引用在離 開作用域時(shí),是不會(huì)釋放對(duì)象的。否則,便無法將一個(gè)對(duì)象直接傳遞到函數(shù)之外。在這種情況下,如果沒有顯式地調(diào)用Close()之類的操作,資源將不會(huì)得到 立刻釋放。但是像文件、鎖、數(shù)據(jù)庫鏈接之類屬于重要或稀缺的資源,如果等到GC執(zhí)行回收,會(huì)造成資源不足。更有甚者,會(huì)造成代碼執(zhí)行上的問題。我曾經(jīng)遇到 過這樣一件事:我執(zhí)行了一個(gè)sql操作,獲得一個(gè)結(jié)果集,然后執(zhí)行下一個(gè)sql,結(jié)果無法執(zhí)行。這是因?yàn)槲沂褂玫腟QL Server 2000不允許在一個(gè)數(shù)據(jù)連接上同時(shí)打開兩個(gè)結(jié)果集(很多數(shù)據(jù)庫引擎都是這樣)。第一個(gè)結(jié)果集用完后沒有立刻釋放,而GC操作則尚未啟動(dòng),于是便造成在一 個(gè)未關(guān)閉結(jié)果集的數(shù)據(jù)連接上無法執(zhí)行新的sql的問題。
                所以,只要涉及了內(nèi)存以外的資源,應(yīng)當(dāng)盡快釋放。(當(dāng)然,如果內(nèi)存能夠盡快釋放,就更好了)。對(duì)于上述CodeWithoutCleanup()函數(shù),應(yīng)當(dāng)在最后調(diào)用file對(duì)象上的Close()函數(shù),以便釋放文件:
                    static void CodeWithoutCleanup()
                    
            {
                        System.IO.FileStream file 
            = null;
                        System.IO.FileInfo fileInfo 
            = new System.IO.FileInfo("C:\file.txt");
                        file 
            = fileInfo.OpenWrite();
                        file.WriteByte(
            0xF);
                        file.Close();
                    }
                現(xiàn)在,這個(gè)函數(shù)是嚴(yán)格資源安全的,但卻不是嚴(yán)格異常安全的。如果在文件的操作中拋出異常,Close()成員將得不到調(diào)用。此時(shí),文件也將無法及時(shí)關(guān)閉,直到GC完成。為此,需要對(duì)異常作出處理:
                    static void CodeWithCleanup()
                    
            {
                        System.IO.FileStream file 
            = null;
                        System.IO.FileInfo fileInfo 
            = null;
                        
            try
                        
            {
                            fileInfo 
            = new System.IO.FileInfo("C:\file.txt");
                            file 
            = fileInfo.OpenWrite();
                            file.WriteByte(
            0xF);
                        }

                        
            catch(System.Exception e)
                        
            {
                            System.Console.WriteLine(e.Message);
                        }

                        
            finally
                        
            {
                            
            if (file != null)
                            
            {
                                file.Close();
                            }

                        }

                   }
                try-catch-finally是處理這種情況的標(biāo)準(zhǔn)語句。但是,相比前面的C++代碼fun1()和fun4()繁瑣很多。這都是沒有RAII的后果啊。下面,我們就來看看,如何在C#整出RAII來。
                一個(gè)有效的RAII應(yīng)當(dāng)包含兩個(gè)部分:構(gòu)造/析構(gòu)函數(shù)的資源獲取/釋放和確定性的析構(gòu)函數(shù)調(diào)用。前者在C#中不成問題,C#有構(gòu)造函數(shù)和析構(gòu)函數(shù)。不過, C#的構(gòu)造函數(shù)和析構(gòu)函數(shù)是不能用于RAII的,原因一會(huì)兒會(huì)看到。正確的做法是讓一個(gè)類實(shí)現(xiàn)IDisposable接口,在IDisposable:: Dispose()函數(shù)中釋放資源:
                    class RAIIFile : IDisposable
                    
            {
                    
            public RAIIFile(string fn) {
                            System.IO.FileInfo fileInfo 
            = new System.IO.FileInfo(fn);
                            file 
            = fileInfo.OpenWrite();
                        }


                    
            public void Dispose() {
                              file.Close();
                          }


                    
            private System.IO.FileStream file = null;
                    }
                下一步,需要確保文件在退出作用域,或發(fā)生異常時(shí)被確定性地釋放。這項(xiàng)工作需要通過C#的using語句實(shí)現(xiàn):
                    static void CodeWithRAII()
                    
            {
                        
            using(RAIIFile file=new RAIIFile("C:\file.txt"))
                        
            {
                            ... 
            //操作文件
                        }
             //文件釋放
                    }
                一旦離開using的作用域,file.Dispose()將被調(diào)用,文件便會(huì)得到釋放,即便拋出異常,亦是如此。相比CodeWithCleanup ()中那坨雜亂繁復(fù)的代碼,CodeWithRAII()簡(jiǎn)直可以算作賞心悅目。更重要的是,代碼的簡(jiǎn)潔和規(guī)則將會(huì)大幅減少出錯(cuò)可能性。值得注意的是 using語句只能作用于實(shí)現(xiàn)IDisposable接口的類,即便實(shí)現(xiàn)了析構(gòu)函數(shù)也不行。所以對(duì)于需要得到RAII的類,必須實(shí)現(xiàn) IDisposable。通常,凡是涉及到資源的類,都應(yīng)該實(shí)現(xiàn)這個(gè)接口,便于日后使用。實(shí)際上,.net庫中的很多與非內(nèi)存資源有關(guān)的類,都實(shí)現(xiàn)了 IDisposable,都可以利用using直接實(shí)現(xiàn)RAII。
                但是,還有一個(gè)問題是using無法解決的,就是如何維持類的成員函數(shù)的RAII。我們希望一個(gè)類的成員對(duì)象在該類實(shí)例創(chuàng)建的時(shí)候獲取資源,而在其銷毀的時(shí)候釋放資源:
                    class X
                    
            {
                    
            public:
                        X():m_file(
            "c:\file.txt"{}
                    
            private:
                        File m_file;    
            //在X的實(shí)例析構(gòu)時(shí)調(diào)用File::~File(),釋放資源。
                    }
                但是在C#中無法實(shí)現(xiàn)。由于uing中實(shí)例化的對(duì)象在離開using域的時(shí)候便釋放了,無法在構(gòu)造函數(shù)中使用:
                    class X
                    
            {
                        
            public X() {
                            
            using(m_file=new RAIIFile("C:\file.txt"))
                            
            {
                            }
            //此處m_file便釋放了,此后m_file便指向無效資源
                        }

                        pravite RAIIFile m_file;
                    }
                對(duì)于成員對(duì)象的RAII只能通過在析構(gòu)函數(shù)或Dispose()中手工地釋放。我還沒有想出更好的辦法來。
                至此,RAII的來龍去脈已經(jīng)說清楚了,在C#里也能從中汲取到充足的養(yǎng)分。但是,這還不是RAII的全部營養(yǎng),RAII還有更多的擴(kuò)展用途。在 《Imperfect C++》一書中,Matthew Wilson展示了RAII的一種非常重要的應(yīng)用。為了不落個(gè)鸚鵡學(xué)舌的名聲,這里我給出一個(gè)真實(shí)遇到的案例,非常簡(jiǎn)單:我寫的程序需要響應(yīng)一個(gè)Grid 控件的CellTextChange事件,執(zhí)行一些運(yùn)算。在響應(yīng)這個(gè)事件(執(zhí)行運(yùn)算)的過程中,不能再響應(yīng)同一個(gè)事件,直到處理結(jié)束。為此,我設(shè)置了一個(gè) 標(biāo)志,用來控制事件響應(yīng):
                    class MyForm
                    
            {
                    
            public:
                        MyForm():is_cacul(
            false{}
                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            is_cacul
            =true;
                            ... 
            //執(zhí)行計(jì)算任務(wù)
                            is_cacul=false;
                        }

                    
            private:
                        
            bool is_cacul;
                    }
            ;
                但是,這里的代碼不是異常安全的。如果在執(zhí)行計(jì)算的過程中拋出異常,那么is_cacul標(biāo)志將永遠(yuǎn)是true。此后,即便是正常的 CellTextChange也無法得到正確地響應(yīng)。同前面遇到的資源問題一樣,傳統(tǒng)上我們不得不求助于try-catch語句。但是如果我們運(yùn)用 RAII,則可以使得代碼簡(jiǎn)化到不能簡(jiǎn)化,安全到不能再安全。我首先做了一個(gè)類:
                    class BoolScope
                    
            {
                    
            public:
                        BoolScope(
            bool& val, bool newVal)
                            :m_val(val), m_old(val) 
            {
                            m_val
            =newVal;
                        }

                        
            ~BoolScope() {
                            m_val
            =m_old;
                        }


                    
            private:
                        
            bool& m_val;
                        
            bool m_old;
                    }
            ;
                這個(gè)類的作用是所謂“域守衛(wèi)(scoping)”,構(gòu)造函數(shù)接受兩個(gè)參數(shù):第一個(gè)是一個(gè)bool對(duì)象的引用,在構(gòu)造函數(shù)中保存在m_val成員里;第二個(gè) 是新的值,將被賦予傳入的那個(gè)bool對(duì)象。而該對(duì)象的原有值,則保存在m_old成員中。析構(gòu)函數(shù)則將m_old的值返還給m_val,也就是那個(gè) bool對(duì)象。有了這個(gè)類之后,便可以很優(yōu)雅地獲得異常安全:
                    class MyForm
                    
            {
                    
            public:
                        MyForm():is_cacul(
            false{}
                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            BoolScope bs_(is_cacul, 
            true);
                            ... 
            //執(zhí)行計(jì)算任務(wù)
                        }

                    
            private:
                        
            bool is_cacul;
                    }
            ;
                好啦,任務(wù)完成。在bs_創(chuàng)建的時(shí)候,is_cacul的值被替換成true,它的舊值保存在bs_對(duì)象中。當(dāng)OnCellTextChange()返回 時(shí),bs_對(duì)象會(huì)被自動(dòng)析構(gòu),析構(gòu)函數(shù)會(huì)自動(dòng)把保存起來的原值重新賦給is_cacul。一切又都回到原先的樣子。同樣,如果異常拋出,is_cacul 的值也會(huì)得到恢復(fù)。
                這個(gè)BoolScope可以在將來繼續(xù)使用,分?jǐn)傁聛淼拈_發(fā)成本幾乎是0。更進(jìn)一步,可以開發(fā)一個(gè)通用的Scope模板,用于所有類型,就像《Imperfect C++》里的那樣。
                下面,讓我們把戰(zhàn)場(chǎng)轉(zhuǎn)移到C#,看看C#是如何實(shí)現(xiàn)域守衛(wèi)的。考慮到C#(.net)的對(duì)象模型的特點(diǎn),我們先實(shí)現(xiàn)引用類型的域守衛(wèi),然后再來看看如何對(duì)付值類型。其原因,一會(huì)兒會(huì)看到。
                我曾經(jīng)需要向一個(gè)grid中填入數(shù)據(jù),但是填入的過程中,控件不斷的刷新,造成閃爍,也影響性能,除非把控件上的AutoDraw屬性設(shè)為false。為此,我做了一個(gè)域守衛(wèi)類,在填寫操作之前關(guān)上AutoDraw,完成或異常拋出時(shí)再打開:
                    class DrawScope : IDisposable
                    
            {
                        
            public DrawScope(Grid g, bool val) {
                            m_grid
            =g;
                            m_old
            =g->AutoDraw;
                            m_grid
            ->AutoDraw=val;
                        }

                        
            public void Dispose() {
                                g
            ->AutoDraw=m_old;
                           }

                        
            private Grid m_grid;
                        
            private bool m_old;
                    }
            ;
                于是,我便可以如下優(yōu)雅地處理AutoDraw屬性設(shè)置問題:
                    static void LoadData(Grid g) {
                        
            using(DrawScope ds=new DrawScope(g, false))
                        
            {
                            ... 
            //執(zhí)行數(shù)據(jù)裝載
                        }

                    }
                現(xiàn)在,我們回過頭,來實(shí)現(xiàn)值類型的域守衛(wèi)。案例還是采用前面的CellTextChange事件。當(dāng)我試圖著手對(duì)那個(gè)is_cacul執(zhí)行域守衛(wèi)時(shí),遇到了不小的麻煩。起初,我寫下了這樣的代碼:
                    class BoolScope
                    
            {
                        
            private ??? m_val; //此處用什么類型?
                        private bool m_old;
                    }
            ;
                m_val應(yīng)當(dāng)是一個(gè)指向一個(gè)對(duì)象的引用,C#是沒有C++那些指針和引用的。在C#中,引用類型定義的對(duì)象實(shí)際上是一個(gè)指向?qū)ο蟮囊?;而值類型定義的 對(duì)象實(shí)際上是一個(gè)對(duì)象,或者說“棧對(duì)象”,但卻沒有一種指向值類型的引用。(關(guān)于這種對(duì)象模型的優(yōu)劣,后面的“題外話”小節(jié)有一些探討)。我嘗試著采用兩 種辦法,一種不成功,而另一種成功了。
                C#(.net)有一種box機(jī)制,可以將一個(gè)值對(duì)象打包,放到堆中創(chuàng)建。這樣,或許可以把一個(gè)值對(duì)象編程引用對(duì)象,構(gòu)成C#可以引用的東西:
                    class BoolScope : IDisposable
                    
            {
                        
            public BoolScope(object val, bool newVal) {
                                m_val
            =val;                 //#1
                                m_old=(bool)val;
                                (
            bool)m_val=newVal;    //#2
                        }

                        
            public void Dispose() {
                                (
            bool)m_val=m_old;    //#3
                           }

                        
            private object m_val;
                        
            private bool m_old;
                    }
                使用時(shí),應(yīng)當(dāng)采用如下形式:
                    class MyForm
                    
            {
                        
            public MyForm() {
                            is_cacul
            =new bool(false); //boxing
                        }

                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            
            using(BoolScope bs=new BoolScope(is_cacul, true))
                            
            {
                                ... 
            //執(zhí)行計(jì)算任務(wù)
                            }

                        }

                        
            private object is_cacul;
                    }
            ;
                很可惜,此路不通。因?yàn)樵诖a#1的地方,并未執(zhí)行引用語義,而執(zhí)行了值語義。也就是說,沒有把val(它是個(gè)引用)的值賦給m_val(也是個(gè)引用), 而是為m_val做了個(gè)副本。以至于在代碼#2和#3處無法將newVal和m_old賦予val(也就是is_cacul)。或許C#的設(shè)計(jì)者有無數(shù)理 由說明這種設(shè)計(jì)的合理性,但是在這里,卻扼殺了一個(gè)非常有用的idom。而且,缺少對(duì)值對(duì)象的引用手段,大大限制了語言的靈活性和擴(kuò)展性。
                第二種方法就非常直白了,也絕對(duì)不應(yīng)當(dāng)出問題,就是使用包裝類:
                    class BoolVal
                    
            {
                        
            public BoolVal(bool v)
                        
            {
                            m_val
            =v;
                        }

                        
            public bool getVal() {
                            
            return m_val;
                        }

                        
            public void setVal(bool v) {
                            m_val
            =v;
                        }

                        
            private bool m_val;
                    }

                    
            class BoolScope : IDisposable
                    
            {
                        
            public IntScope(BoolVal iv, bool v)
                        
            {
                            m_old 
            = iv.getVal();
                            m_Val 
            = iv;
                            m_Val.setVal(v);
                        }

                        
            public virtual void Dispose()
                        
            {
                            m_Val.setVal(m_old);
                        }

                        
            private BoolVal m_Val;
                        
            private bool m_old;
                    }
                這里,我做了一個(gè)包裝類BoolVal,是個(gè)引用類。然后以此為基礎(chǔ),編寫了一個(gè)BoolScope類。然后,便可以正常使用域守衛(wèi):
                    class MyForm
                    
            {
                        
            public MyForm() {
                            m_val.setVal(
            false); //boxing
                        }

                        ...
                        
            void OnCellTextChange(Cell& cell) {
                            
            if(is_cacul)
                                
            return;
                            
            using(BoolScope bs=new BoolScope(m_val, true))
                            
            {
                                ... 
            //執(zhí)行計(jì)算任務(wù)
                            }

                        }

                        
            private BoolVal m_val;
                    }
            ;
                好了,一切都很不錯(cuò)。盡管C#的對(duì)象模型給我們平添了不少麻煩,使得我多寫了不少代碼,但是使用域守衛(wèi)類仍然是一本萬利的事情。作為GP fans,我當(dāng)然也嘗試著在C#里做一些泛型,以免去反復(fù)開發(fā)包裝類和域守衛(wèi)類的苦惱。這些東西,就留給大家做練習(xí)吧。:)
                在某些場(chǎng)合下,我們可能會(huì)對(duì)一些對(duì)象做一些操作,完事后在恢復(fù)這個(gè)對(duì)象的原始狀態(tài),這也是域守衛(wèi)類的用武之地。只是守衛(wèi)一個(gè)結(jié)構(gòu)復(fù)雜的類,不是一件輕松的 工作。最直接的做法是取出所有的成員數(shù)據(jù),在結(jié)束后再重新復(fù)制回去。這當(dāng)然是繁復(fù)的工作,而且效率不高。但是,我們將在下一篇看到,如果運(yùn)用swap手 法,結(jié)合復(fù)制構(gòu)造函數(shù),可以很方便地實(shí)現(xiàn)這種域守衛(wèi)。這我們以后再說。
                域守衛(wèi)作為RAII的一個(gè)擴(kuò)展應(yīng)用,非常簡(jiǎn)單,但卻極具實(shí)用性。如果我們對(duì)“資源”這個(gè)概念加以推廣,把一些值、狀態(tài)等等內(nèi)容都納入資源的范疇,那么域守衛(wèi)類的使用是順理成章的事。

            題外話:C#的對(duì)象模型

                C#的設(shè)計(jì)理念是簡(jiǎn)化語言的學(xué)習(xí)和使用。但是,就前面案例中出現(xiàn)的問題而言,在特定的情況下,特別是需要靈活和擴(kuò)展的時(shí)候,C#往往表現(xiàn)的差強(qiáng)人意。C# 的對(duì)象模型實(shí)際上是以堆對(duì)象和引用語義為核心的。不過,考慮到維持堆對(duì)象的巨大開銷和性能損失,應(yīng)用在一些簡(jiǎn)單的類型上,比如int、float等等,實(shí) 在得不嘗失。為此,C#將這些簡(jiǎn)單類型直接作為值處理,當(dāng)然也允許用戶定義自己的值類型。值類型擁有值語義。而值類型的本質(zhì)是棧對(duì)象,引用類型則是堆對(duì) 象。
                這樣看起來應(yīng)該是個(gè)不錯(cuò)的折中,但是實(shí)際上卻造成了不大不小的麻煩。前面的案例已經(jīng)明確地表現(xiàn)了這種對(duì)象模型引發(fā)的麻煩。由于C#拋棄值和引用的差異(為 了簡(jiǎn)化語言的學(xué)習(xí)和使用),那么對(duì)于一個(gè)引用對(duì)象,我們無法用值語義訪問它;而對(duì)于一個(gè)值對(duì)象,我們無法用引用語義訪問。對(duì)于前者,不會(huì)引發(fā)本質(zhì)性的問 題,因?yàn)槲覀兛梢允褂贸蓡T函數(shù)來實(shí)現(xiàn)值語義。但是對(duì)于后者,則是無法逾越的障礙,就像在BoolScope案例中表現(xiàn)的那樣。在這種情況下,我們不得不用 引用類包裝值類型,使得值類型喪失了原有的性能和資源優(yōu)勢(shì)。
                更有甚者,C#的對(duì)象模型有時(shí)會(huì)造成語義上的沖突。由于值類型使用值語義,而引用類型使用引用語義。那么同樣是對(duì)象定義,便有可能使用不同的語義:
                    int i, j=10;  //值類型
                    i=j;            //值語義,兩個(gè)對(duì)象復(fù)制內(nèi)容
                    i=5;           //i==5, j==10
                    StringBuilder s1, s2 = new StringBuilder("s2");   //引用類型
                    s1 = s2;        //引用語義,s1和s2指向同一個(gè)對(duì)象
                    s1.Append(" is s1");    //s1==s2=="s1 is s2"
                同一個(gè)形式具有不同語義,往往會(huì)造成意想不到的問題。比如,在軟件開發(fā)的最初時(shí)刻,我們認(rèn)為某個(gè)類型是值類型就足夠了,還可以獲得性能上的好處。但是,隨 著項(xiàng)目進(jìn)入后期階段,發(fā)現(xiàn)最初的設(shè)計(jì)有問題,值類型限制了該類型的某些特性(如不能擁有析構(gòu)函數(shù),不能引用等等),那么需要把它改成引用類型。于是便引發(fā) 一大堆麻煩,需要檢查所有使用該類型的代碼,然后把賦值操作改成復(fù)制操作。這肯定不是討人喜歡的工作。為此,在實(shí)際開發(fā)中,很少自定義值類型,以免將來自縛手腳。于是,值類型除了語言內(nèi)置類型和.net庫預(yù)定義的類型外,成了一件擺設(shè)。
                相比之下,傳統(tǒng)語言,如Ada、C、C++、Pascal等,區(qū)分引用和值的做法盡管需要初學(xué)者花更多的精力理解其中的差別,但在使用中則更加妥善和安全。畢竟學(xué)習(xí)是暫時(shí)的,使用則是永遠(yuǎn)的。
            posted on 2008-02-16 08:19 longshanks 閱讀(2058) 評(píng)論(2)  編輯 收藏 引用

            Feedback

            # re: C++的營養(yǎng) 2008-02-16 11:47 abettor
            用對(duì)照的方法學(xué)習(xí)確實(shí)是一個(gè)很不錯(cuò)的方法。  回復(fù)  更多評(píng)論
              

            # re: C++的營養(yǎng) 2008-02-22 16:45 i
            file pfile=new file("my.txt");
            應(yīng)該改為
            file *pfile=new file("my.txt");  回復(fù)  更多評(píng)論
              


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


            色综合久久最新中文字幕| 国产精品久久久久久久久软件 | 久久99久国产麻精品66| 久久人妻无码中文字幕| 伊人久久久AV老熟妇色| 老司机国内精品久久久久| 久久国产精品免费一区| 亚洲综合伊人久久大杳蕉| 久久久久免费精品国产| 天天影视色香欲综合久久| 色婷婷久久综合中文久久蜜桃av| www.久久99| 亚洲人成精品久久久久| 99久久国产免费福利| 国产毛片欧美毛片久久久 | 久久99精品国产| 久久久久久免费视频| 久久亚洲国产欧洲精品一| 亚洲精品无码久久千人斩| 久久se这里只有精品| 精品久久8x国产免费观看| 一本综合久久国产二区| 国产成人久久久精品二区三区| 一本久久a久久精品亚洲| 久久男人AV资源网站| 久久久久久综合一区中文字幕| 欧美日韩精品久久久免费观看| 99久久免费只有精品国产| 精品一区二区久久久久久久网站| 狠狠精品久久久无码中文字幕| 久久精品国产亚洲AV不卡| 久久精品国产亚洲av日韩| 男女久久久国产一区二区三区| 噜噜噜色噜噜噜久久| 久久国产热这里只有精品| 激情五月综合综合久久69| 国产福利电影一区二区三区久久久久成人精品综合 | 亚洲va久久久噜噜噜久久天堂| 亚洲欧美国产精品专区久久| 亚洲AV伊人久久青青草原| 亚洲欧洲中文日韩久久AV乱码|