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

            zhuweisky

            思考、探索、實踐...... Rumination on C++
            posts - 2, comments - 22, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            純虛函數(shù)能為private嗎?

            Posted on 2005-09-14 21:32 zhuweisky 閱讀(5895) 評論(17)  編輯 收藏 引用 所屬分類: C++組件技術(shù)

            我們把一個僅僅含有純虛函數(shù)的類稱為接口,我們也好像已經(jīng)習(xí)慣了將這個接口中的所有純虛函數(shù)全聲明為public,而且按照這樣的設(shè)計,一切都工作得不錯。比如COM正是這樣做的,它的接口中幾乎不會存在private的純虛函數(shù)。那么,讓我們想一想,純虛函數(shù)或者虛函數(shù)可以為private嗎?如果這種方式是可行的,那么什么時候可以將(純)虛函數(shù)設(shè)為private了?這些都是本文將要討論的主題。一起來看看。


            一.訪問限定符與繼承
              
             

            如果基類隱式(間接)向子類暴露了私有成員,那么從某種意義上講,該私有成員對于子類是可見的。

            任何一本講C++基礎(chǔ)的課本上都詳細(xì)地介紹了訪問限定符與繼承的關(guān)系,在這里就不重復(fù)了,但是,課本上的東西并不全,不信?那么請先看看下面的例子:

            #include <string>  
            #include 
            <iostream>
             
            using namespace
             std ; 

            class
             Base 

            private

                
            string classID() const

                { 
                   
            return string("Base") ; 
                } 

            protected

                
            virtual void doWork() =0 ; //純虛函數(shù)


            public:  
                
            void
             work() 
                { 
                    cout
            <<"this class id is "<<classID()<<
            endl ;  
                    doWork() ; 
                } 

                
            virtual ~
            Base() 
                { 
                } 
            }; 

            class DerivedA : public
             Base 

            private

                
            string classID() const
              
                { 
                   
            return string("DerivedA"
            ) ;
                } 

            protected

                
            void
             doWork() 
                { 

                   cout
            <<"this is DerivedA doWork !"<<
            endl ; 
                } 
            };

                以上的代碼聲明了一個基類和一個子類,不過比較奇特的是基類的提供的公共接口是非虛的,而這個非虛的公共接口卻調(diào)用了一個非虛的私有函數(shù)和一個虛擬的保護(hù)函數(shù)。接著,子類重定義了這兩個函數(shù)。那么下面的調(diào)用會輸出什么了?   

            Base* bp = new DerivedA() ; 
            bp
            ->work() ;  
            delete bp ; 

               以下是輸出的結(jié)果:
               this class id is Base 
               this is DerivedA doWork ! 

               怎么回事?為什么不是
             
                  this class id is DerivedA 
                  this is DerivedA doWork !

            子類的classID()不是將基類的classID()覆蓋了么?我們來分析一下,基類中的公共的work()成員函數(shù)調(diào)用了私有的classID()成員函數(shù),根據(jù)輸出的結(jié)果來看,在子類中定義的classID方法并沒有覆蓋基類的同名方法,為什么呢?難道是因為classIDprivate導(dǎo)致的?那好,我們將classID函數(shù)改為public再次運行,我們期望的結(jié)果出現(xiàn)了嗎?呵呵,很抱歉,沒有,希望再次破滅了,為什么會這樣?這主要涉及的原因是:普通函數(shù)的調(diào)用是在編譯期確定的,當(dāng)work函數(shù)一看到所調(diào)用的classID是非虛的,就會毫無疑問地去直接使用基類的classID。這一切與Base類是否會被繼承沒有任何關(guān)系,跟Base類被繼承后子類會否再次定義classID就更沒有關(guān)系了。

               那么這種情況下,
            Base類將classID聲明為privatepublic/protected有什么區(qū)別了?當(dāng)將classID聲明為private時,DerivedA看不到基類的classID的聲明,所以不會發(fā)生重定義;當(dāng)將classID聲明為public/protected時,DerivedA將看到基類的classID聲明,于是會發(fā)生重定義,即會覆蓋調(diào)基類的classID的定義。講到這里就要提一下,如果當(dāng)將classID聲明為public/protected,并且子類也定義同名的函數(shù)classID,但是子類的classID與基類的classID的函數(shù)簽名不同,那么此時發(fā)生的將是函數(shù)重載而不是覆蓋。

            讓我們更進(jìn)一步,將基類和子類的classID聲明都改為virtual public ,再次運行程序,會得到以下輸出: 
                     this class id is DerivedA 
                     this is DerivedA doWork !

            而這正是我們所期望的,不是嗎?這其中的原因也很容易理解,因為classIDvirtual ,并且是public的,所以會產(chǎn)生多態(tài)調(diào)用。
               
            再往下走,將基類和子類的classID聲明改為virtual private ,再次運行程序,看看輸出了什么。
                  this class id is DerivedA 
                  this is DerivedA doWork !

            沒有變化,將classID聲明為virtual private和聲明為 virtual public 得到的結(jié)果是一樣的。“為什么會這樣,classIDprivate啊?”你驚訝地叫出來。是,classIDprivate,但classID也是virtual,原因就在這里,用基類指針或引用進(jìn)行虛函數(shù)調(diào)用采用的是動態(tài)綁定,看看編譯器為調(diào)用classID產(chǎn)生的代碼就知道了:

            //c++偽碼

            (this->vptr[1])() ;

            在運行時期,通過this指針將會找到正確的vtbl,即DerivedA類的vtbl,這樣自然就會出現(xiàn)上面的結(jié)果了。那么將classID 聲明為private限制了什么?和將非虛函數(shù)聲明為private一樣,這將使得在Base類外部無法調(diào)用多態(tài)函數(shù)classID,只能在Base內(nèi)部調(diào)用,如通過work函數(shù)調(diào)用。

               可見,多態(tài)性與將實現(xiàn)多態(tài)的函數(shù)的訪問限定符沒有任何關(guān)系,private 函數(shù)仍然可以實現(xiàn)多態(tài),它的指針仍然位于vtbl中,只不過該函數(shù)的多態(tài)一般只能在基類的內(nèi)部由其他非虛函數(shù)調(diào)用該函數(shù)的時候反映出來,訪問限定符僅僅限制外部對類的成員的訪問權(quán)限,它并沒有破壞以下規(guī)則: 

                   
            通過基類指針或引用調(diào)用成員函數(shù)時,如果該函數(shù)時非虛的,那么將采用靜態(tài)綁定,即編譯時綁定;如果該函數(shù)是虛擬的,則采用動態(tài)綁定,即運行時綁定。

            二.virtual與訪問限定符結(jié)合

            上面我們通過分析,已經(jīng)知道了多態(tài)的實現(xiàn)與訪問限定符沒有任何關(guān)系,訪問限定符只是控制類的成員對外部的可見性,但不限制多態(tài)。正如上面提到的,將classID聲明為virtual private和聲明為 virtual public 后再次運行程序,得到的結(jié)果是一樣的,上面我們簡單的地分析了一下表面現(xiàn)象,但這個問題決不是這么簡單,讓我們挖掘更深層次的意義,我想這應(yīng)該屬于OOAOOD的范疇了。好,讓我們一步步看過來。

            當(dāng)我們將classID聲明為非虛的 private時,子類將看不見它,當(dāng)然也就無法覆蓋或重載它,即在這中情況下,子類無法更改classID的實現(xiàn),但是子類繼承了公共接口work(),而這個接口調(diào)用了classID,所以,可以看作,子類間接地繼承了classID的實現(xiàn),并且這個實現(xiàn)是無法修改的。于是,我可以說,基類中聲明一個普通私有成員函數(shù),表示這是一個不可被更改的實現(xiàn)細(xì)節(jié)。

            再來討論將classID聲明為virtual private的情況,聲明為private表示基類不想讓子類看到這個函數(shù),但是又聲明為virtual,表示基類想讓這個函數(shù)實現(xiàn)多態(tài)。呵呵,基類既想實現(xiàn)多態(tài),卻又不讓子類看見這個函數(shù),這似乎有點自相矛盾,是嗎?其實,這其中的意思是,子類既可以修改這個實現(xiàn),也可以繼承其基類默認(rèn)的實現(xiàn)。所以可以這么說,如果基類中有一個虛擬私有成員函數(shù),表示這是一個“可以”被派生類修改的實現(xiàn)細(xì)節(jié)。注意,當(dāng)中的用詞,是“可以”,而不是別的。

            最后來看看將classID聲明為virtual protected的情況。將classID聲明為protected表示基類“需要”子類看見這個函數(shù),注意,我使用“需要”這個動詞,這個詞表示了一定的“強(qiáng)制”意味。與將classID聲明為virtual private的情況對比一下,我想你已經(jīng)知道答案了,即是,如果基類中有一個虛擬保護(hù)成員函數(shù),表示這是一個必須被派生類修改的實現(xiàn)細(xì)節(jié)。“必須”這個詞表達(dá)了強(qiáng)制的意思。

            關(guān)于“virtual與訪問限定符結(jié)合”的問題就討論這么多,你也許說,還漏掉了將classID聲明為virtual public的情況。是的,其實,我并不推薦將虛擬函數(shù)聲明為public,盡管這種方式在現(xiàn)在很流行,我推薦將其使用virtual protected來替換,這就說明基類必須另外發(fā)布一個幾乎不更改的非虛public接口,在這個接口中調(diào)用了virtual protectedvirtual private函數(shù),這樣以來,我們就對類的內(nèi)部實現(xiàn)作了進(jìn)一步的隱藏,而這無論是對系統(tǒng)的可擴(kuò)展性,還是可維護(hù)性都是大有幫助的。“虛擬函數(shù)應(yīng)該和數(shù)據(jù)成員一樣對待――讓他們成為私有的,除非設(shè)計需求表明應(yīng)該有較少的限制。提升它們到更高存取級別比把它們降到更私有的級別更容易些。”

            最后,把上面所說的小結(jié)一下:

                  基類中的一個普通私有成員函數(shù),表示這是一個不可被更改的實現(xiàn)細(xì)節(jié)。 
                  基類中的一個虛擬私有成員函數(shù),表示這是一個可以被派生類修改的實現(xiàn)細(xì)節(jié)。
                  基類中的一個虛擬保護(hù)成員函數(shù),表示這是一個必須被派生類修改的實現(xiàn)細(xì)節(jié)。 
               
               最好不要將虛擬成員函數(shù)聲明為public,而是用protected來替換。

            三.模板方法模式

            在理解了上面所述的內(nèi)容的情況下,再來理解模板方法模式就非常easy了,模板方法是在GOF的經(jīng)典大作《設(shè)計模式》中闡述了一種模式,該模式定義了一個操作中的算法的骨架,而將一些步驟的實現(xiàn)延遲到子類中,模板方法使得派生類可以不改變一個算法的結(jié)構(gòu)即可重定義算法的某些特定步驟。在這里,我不想再重復(fù)解釋這個模式如何實現(xiàn)的,我僅僅舉個例子,這個例子將體現(xiàn)出模板方法中最重要的思想。

            假設(shè)基類定義的一個算法的骨架由3個步驟完成,其中第一個步驟是該繼承體系中不可被改變的一個步驟,即所有的類對該步驟的實現(xiàn)都是一樣的,那個這個步驟可以設(shè)置為非虛的private ;第二個步驟是一個可以被派生類改寫也可以不被改寫的步驟,通過上面的討論知道,可以將其設(shè)為virtual private ;第三個步驟是針對每一個派生類的實現(xiàn)都不同,那么這個步驟可以被設(shè)為virtual protected ,而且,步驟三只能針對特定的派生類才有意義,所以將步驟三也設(shè)為純虛函數(shù)。如下面的代碼所示:

             

            class BaseTemplate 

            private


                
            void step1(void)  // 不可被更改的實現(xiàn)細(xì)節(jié) 

                { 
                     
                } 
                
            virtual void step2(void ) // 可以被派生類修改的實現(xiàn)細(xì)節(jié) 

                { 
                     
                } 

            protected

                
            virtual void step3(void ) =0// 必須被派生類修改的實現(xiàn)細(xì)節(jié)     


            public
                
            void work(void// 骨架函數(shù),實現(xiàn)了骨架 

                { 
                   step1() ; 
                   step2() ; 
                   step3() ;     
                } 
            };    


               注意,上例中根本沒有暴露任何虛函數(shù),所有的這一切都是通過work()這個非虛的public接口展現(xiàn)出來的,當(dāng)我們用一個BaseTemplate指針調(diào)用work()時,表面上是一個非虛函數(shù)調(diào)用,采用靜態(tài)綁定,事實上也正是這樣,但是,這個調(diào)用的背后隱藏的卻是多態(tài)調(diào)用,即step2step3動態(tài)綁定了。看見,采用模板方法模式,不僅定義了一個算法的骨架,而且把這個骨架的實現(xiàn)的細(xì)節(jié)作了進(jìn)一步的封裝。我們可以在模板方法模式中可以這樣設(shè)計:

            (1)       如果一個函數(shù)作為算法骨架中不可變更的一部分,那么可以將此函數(shù)作為基類的私有函數(shù),并且在基類的公共骨架函數(shù)中調(diào)用該函數(shù),即該函數(shù)作為骨架的一個不可更改的實現(xiàn)細(xì)節(jié)。

            (2)       如果一個函數(shù)提供了算法骨架某環(huán)節(jié)的一個缺省實現(xiàn),那么可以考慮將該函數(shù)作為基類的私有虛函數(shù),表示子類可以改寫它,也可以不改寫它。

            (3)       如果作為算法骨架一部分某個函數(shù)要求在子類中擁有不同的實現(xiàn),那么可以考慮將該函數(shù)作為基類的保護(hù)(純)虛函數(shù),表示子類必須改寫它。

            講到這里,已經(jīng)差不多了,在結(jié)束的時候,提一下語法與語義的聯(lián)系。通常,語法是表象,語義是表象后面隱藏的東西,而這些隱藏的語義往往更具有價值。舉個例子,public繼承與private繼承在語法方面似乎沒有什么更多的東西值得探討,它們的區(qū)別僅僅在于改變了繼承得到的成員的可見性,但是從語義方面來分析,它們就相差太遠(yuǎn)了,private繼承在語義上來講是“通過基類來實現(xiàn)自己”,即是“實現(xiàn)繼承”,在這種繼承關(guān)系中,基類和子類的關(guān)系是很薄弱的;而public繼承在語義上即是我們所熟知的“IS-A”關(guān)系,它體現(xiàn)了基類和子類之間的親密性,也正是這種“IS-A”關(guān)系為多態(tài)性提供了基礎(chǔ)。

            所以,通過表面的語法來挖掘其背后的語義很有意義,就像這篇文章中提到的將訪問限定符與virtual結(jié)合起來的語法背后隱藏的語義,挖掘出這些語義,對于我們以后在進(jìn)行設(shè)計時作恰當(dāng)?shù)木駬駸o疑是大有幫助的。


            --zhuweisky 2003.04.18

            Feedback

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-04-16 13:32 by lkhg
            hk,nnb,nb,khgdfmgsdkkopx,cv flkgbp[fdl p[lgp[dlfp[dsgfsdgbvs

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-04-16 18:51 by ZiDing
            嗯,不錯不錯。

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-04-17 17:48 by 千里馬肝
            LZ的論點即是《Exceptional C++ Style中文版》的第17、18、19節(jié) 的進(jìn)一步描述

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-05-02 21:51 by 田地
            關(guān)于“基類中的一個虛擬保護(hù)成員函數(shù)”,protected表示基類“需要”子類看見這個函數(shù),也就是派生類的其他函數(shù)可以調(diào)用這個虛擬函數(shù)吧?派生類可以修改也可以不修改吧?如果是必須修改,只能依靠聲明為純虛函數(shù)來“強(qiáng)制”吧?

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-10-12 09:43 by dxt
            受用,不錯

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-11-04 14:24 by mumutou
            我覺得他說的不對,
            class BaseTemplate
            {
            private:

            void step1(void) // 不可被更改的實現(xiàn)細(xì)節(jié)
            {

            }
            virtual void step2(void ) // 可以被派生類修改的實現(xiàn)細(xì)節(jié)
            {

            }

            protected:
            virtual void step3(void ) =0; // 必須被派生類修改的實現(xiàn)細(xì)節(jié)

            public:
            void work(void) // 骨架函數(shù),實現(xiàn)了骨架
            {
            step1() ;
            step2() ;
            step3() ;
            }
            };
            protected:
            virtual void step3(void ) =0; // 必須被派生類修改的實現(xiàn)細(xì)節(jié)
            這是一個pure virtual 函數(shù),當(dāng)然必須被派生類修改了,不然怎么用

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-11-04 14:32 by mumutou
            #include <string>
            #include <iostream>
            using namespace std ;

            class Base
            {
            protected:
            virtual string classID() const
            {
            return string("Base") ;
            }

            protected:
            virtual void doWork() =0 ; //純虛函數(shù)

            public:
            void work()
            {
            cout<<"this class id is "<<classID()<<endl ;
            doWork() ;
            }

            virtual ~Base()
            {
            }
            };

            class DerivedA : public Base
            {
            protected:
            void doWork()
            {

            cout<<"this is DerivedA doWork !"<<endl ;
            }
            };


            void main(int argc, char* argv[])
            {
            DerivedA* bp = new DerivedA() ;
            bp->work() ;
            delete bp ;
            }
            基類中的一個虛擬保護(hù)成員函數(shù),表示這是一個必須被派生類修改的實現(xiàn)細(xì)節(jié)。
            不然的話,調(diào)用的還是Base 的classID
            這樣理解就對了

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2006-12-17 21:59 by 天吻
            很受用.頂啊!!!

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2007-01-17 15:33 by 黃劍雄
            "講到這里就要提一下,如果當(dāng)將classID聲明為public/protected,并且子類也定義同名的函數(shù)classID,但是子類的classID與基類的classID的函數(shù)簽名不同,那么此時發(fā)生的將是函數(shù)重載而不是覆蓋。"

            這句話是什么意思?為什么是重載,派生類的函數(shù)能重載基類的函數(shù)??
            重載是對同一個類域的不同函數(shù)來說吧

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2007-02-01 11:29 by zhaoflying
            @黃劍雄
            樓主寫錯了,不是重載,而是隱藏。事實上子類會把基類中的這個同名函數(shù)給隱藏掉,試圖通過子類對象來調(diào)用基類的classID()會報編譯錯誤,但可以通過子類對象調(diào)用自己的classID()。所以這種做法是不合適的。

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2007-07-14 16:05 by juv
            樓主內(nèi)容相當(dāng)精彩,但是呢,如同樓上所說:不是覆蓋和重載,是隱藏!

            還有:
            基類中的一個虛擬私有成員函數(shù),表示這是一個可以被派生類修改的實現(xiàn)細(xì)節(jié)。
            基類中的一個虛擬保護(hù)成員函數(shù),表示這是一個必須被派生類修改的實現(xiàn)細(xì)節(jié)。


            不敢茍同!建議樓主看EFFETIVE C++ 第三版條款34!

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2007-09-20 18:04 by asd
            派生類public繼承時,基類成員函數(shù)private還是public沒有區(qū)別,摟主混淆概念了!

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2007-12-01 22:11 by gules
            virtual函數(shù)的訪問控制級別具有隱含的意義:一個protected virtual function告訴你:“你寫的派生類應(yīng)該,可是說是必須調(diào)用我(基類)的實現(xiàn)。”而一個private virtual function是在說:“派生類可以覆蓋,也可以不覆蓋我,隨你的便。但是你不可以調(diào)用我的實現(xiàn)。”

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2011-03-14 16:45 by suantou
            強(qiáng)人啊 膜拜一下 好文章

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2011-10-08 18:41 by www
            第一段示例代碼并不怎么樣,顯然偏離主旨,一個classID()顯然不是虛函數(shù)

            比較:
            以下是輸出的結(jié)果:
            this class id is Base
            this is DerivedA doWork !

            怎么回事?為什么不是
            this class id is DerivedA
            this is DerivedA doWork !

            區(qū)別只在于Base 和DerivedA ,沒能說明什么,宜刪掉此部分。

            # re: 純虛函數(shù)能為private嗎?[未登錄]  回復(fù)  更多評論   

            2011-12-30 20:57 by kevin
            寫得非常好啊,受益匪淺

            # re: 純虛函數(shù)能為private嗎?  回復(fù)  更多評論   

            2014-01-17 15:21 by 餓半肚
            好吧,當(dāng)我沒看過。。忘記掉忘記掉。。。
            人妻精品久久久久中文字幕| 亚洲精品乱码久久久久66| 精品国产热久久久福利| 热久久国产欧美一区二区精品| 伊人色综合久久天天网| 97精品伊人久久大香线蕉app | 性做久久久久久久| 青青草国产精品久久久久| 一本色道久久综合| 91亚洲国产成人久久精品| 国产成人综合久久精品红| 国产精品久久久久久影院| yy6080久久| 久久精品亚洲乱码伦伦中文| 久久99精品久久久久久动态图 | 亚洲国产精品无码久久一线| 青青青青久久精品国产h| 成人久久免费网站| 污污内射久久一区二区欧美日韩| 72种姿势欧美久久久久大黄蕉| 婷婷国产天堂久久综合五月| 99久久免费国产精品| 久久天天躁狠狠躁夜夜96流白浆 | 日韩精品久久无码人妻中文字幕| 久久精品国产亚洲精品| 久久亚洲精品中文字幕三区| 久久综合噜噜激激的五月天| 波多野结衣久久一区二区| 看全色黄大色大片免费久久久| 久久er热视频在这里精品| 久久久久久人妻无码| 久久人爽人人爽人人片AV| 久久精品亚洲AV久久久无码| 亚洲国产成人久久精品99 | 88久久精品无码一区二区毛片| 九九久久自然熟的香蕉图片| 亚洲国产另类久久久精品黑人 | 久久91精品国产91久久小草| 久久久久人妻一区精品性色av| 亚洲中文字幕无码久久2020| 久久这里只有精品18|