• <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>
            隨筆-5  評論-33  文章-0  trackbacks-0
            為了更好的討論這個問題,假設(shè)我們要設(shè)計一個關(guān)于people的class,并進行如下約定:
            1. age 小于 0 的baby不在我們的考慮范圍之內(nèi).
            2. age 大于 200 的people我們認(rèn)為是不存在的.
            即,關(guān)于people的age,我們考慮的范圍是[0 , 200]
            基于以上假設(shè),我們設(shè)計如下一個class,這里為了簡化代碼,突出問題的主體,將部分成員函數(shù)及成員變量去掉.

            class People
            {
                
            public:
                    
            explicit People( int iAge )
                    : m_iAge( iAge )
                    
            {
                    
                    }

            }
            ;

            看起來很簡單,下一步我們可以使用這個class了.

            People janice( 28 );
            janice.Sing();

            如果類似于上面這樣以合法的參數(shù)構(gòu)造一個People,那么后續(xù)的操作也在我們的可控范圍之內(nèi),也是理想的情況.
            但是如果我們以非法的參數(shù)構(gòu)造People并進行相關(guān)的函數(shù)調(diào)用,如下:

            People hebe( -1 );
            hebe.Sing();

            People ella( 
            201 );
            ella.Dance();


            那么這樣實在是太糟糕了,因為在調(diào)用Sing()和Dancing()時我們甚至不知道調(diào)用的對象的內(nèi)部狀態(tài)是非法的,這也就成為bug的一個源泉.

            總結(jié)一下上面的例子中遇到的問題:在以特定的參數(shù)構(gòu)造一個對象時,如果參數(shù)非法或構(gòu)造失敗,我們應(yīng)當(dāng)向調(diào)用者反饋這一信息.

            對于一般的成員函數(shù),如果能夠有返回值,那么我們可以通過返回值來標(biāo)識傳遞給函數(shù)的參數(shù)非法或內(nèi)部運行失敗這種情況.
            但是對于構(gòu)造函數(shù),因為它不能有返回值,所以,我們必須使用其它的方法來向調(diào)用者反饋"傳遞給構(gòu)造函數(shù)的參數(shù)非法或構(gòu)造失敗".

            針對于這一問題有三種解決方案:

            第一種方案:在構(gòu)造函數(shù)的參數(shù)中傳遞一個額外的參數(shù)用于標(biāo)識構(gòu)造是否成功.
            在這種方案的指導(dǎo)下,代碼如下:

            class People
            {
                
            public:
                    People( 
            int iAge , bool &bInitStatus )
                    : m_iAge( iAge )
                    
            {
                        
            if( ( iAge < 0 ) || ( iAge > 200 ) )
                        
            {
                            bInitStatus 
            = false;
                        }

                        
            else
                        
            {
                            bInitStatus 
            = true;
                        }

                    }

            }
            ;


            然后我們可以這樣使用:

            bool bInitStatus = false;
            People hebe( 
            -1 , bInitStatus );
            iffalse == bInitStatus )
            {
                
            // handle error
            }

            else
            {
                hebe.Sing();
            }


            這種方法是可行的,但是代碼看起來過于丑陋且不夠直觀,這里只是作為一種方案提出并不推薦使用.

            第二種方案:使用兩段構(gòu)造的形式.
            所謂的兩段構(gòu)造是指一個對象的內(nèi)部狀態(tài)的初始化分兩步完成,將構(gòu)造函數(shù)中的部分初始化操作轉(zhuǎn)移到一個輔助初始化的成員函數(shù)中:
            第一步是通過構(gòu)造函數(shù)來完成部分內(nèi)部狀態(tài)的初始化.
            第二步是通過類似于 Initialize 之類的成員函數(shù)來完成對象內(nèi)部狀態(tài)的最終初始化.

            兩段構(gòu)造的形式在 MFC 中廣泛使用.在MFC中我們經(jīng)??吹筋愃朴?Initialize , Create 之類的函數(shù).
            基于兩段構(gòu)造的形式,代碼如下:

            class People
            {
                
            public:
                    People( 
            void )
                    : m_iAge( INVALID_AGE )
                    
            {
                    
                    }

                    
                    
            bool Initialize( int iAge )
                    
            {
                        
            if( ( iAge < 0 ) || ( iAge > 200 ) )
                        
            {
                            
            return false;
                        }

                        
            else
                        
            {
                            m_iAge 
            = iAge;
                            
            return true;
                        }
                        
                    }

            }
            ;


            在這種情況下,我們應(yīng)當(dāng)這樣來使用People:

            People hebe;
            const bool bStatus = hebe.Initialize( 20 );
            iffalse == bInitStatus )
            {
                
            // handle error
            }

            else
            {
                hebe.Sing();
            }


            這種方案似乎比第一種方案更優(yōu),但是仍有一個潛在的問題:對象是以兩步構(gòu)造完成的.
            第一步構(gòu)造是由構(gòu)造函數(shù)來完的,OK,這一點我們不用擔(dān)心,編譯器幫我們保證.
            但是第二步是由類似于 Initialize 之類的成員函數(shù)來完成的,如果我們在構(gòu)造一個People對象之后忘記了調(diào)用 Initialize ,
            那么這個對象的內(nèi)部狀態(tài)仍然是非法的,后續(xù)的操作也將由此引發(fā)bug.這也是"兩段構(gòu)造"這種形式受到詬病的原因之一.
            另一方面,"兩段構(gòu)造"的形式與C++的"RAII",Resource Acquisition Is Initialization(資源獲取即初始化),這一原則相違背.
            因為以"兩段構(gòu)造"這種形式設(shè)計的class People 在構(gòu)造一個對象時,它的內(nèi)部狀態(tài)實際上并沒有完全初始化,我們需要調(diào)用 Initialize 來輔助完成最終的初始化.
            所以,盡管"兩段構(gòu)造"這種方案可以解決我們所遇到的"對構(gòu)造函數(shù)參數(shù)非法進行反饋"這個問題,但是這種方案并不夠優(yōu)雅.

            但是為什么MFC會先擇"兩段構(gòu)造"這種形式呢,因為在C++發(fā)展的初期,當(dāng)異常機制還不是足夠成熟,沒有得到廣泛的認(rèn)可和使用時,
            MFC中選擇兩段構(gòu)造或許也是情理之中的,也許還有其它的原因,類似的類庫還有ACE...

            當(dāng)然,在某些情況下,使用兩段構(gòu)造也有其獨到的好處.
            下面所設(shè)計的場景可能有一些牽強,但只是為了力求簡單并能夠說明問題.(代碼進行了大量簡化)

            class Server
            {
                
            public:
                    Server(
            void)
                    
            {
                        
            // allocate a huge chunk of memory to store data
                    }

                    
                    
            ~Server( void )
                    
            {
                        
            // free all the used resource
                    }

            }
            ;


            然后在我們的系統(tǒng)中,我們需要使用一個 server pool , 在系統(tǒng)啟動時,我們需要 server pool 中有 100 個 Server 可用.

            Server serverPool[ 100 ];


            在系統(tǒng)負(fù)載最大的時候,假定100個Server可以勝任,但是在大多數(shù)情況下,我們只需要少量的Server即可以完成任務(wù).
            在這種情況下: Server serverPool[ 100 ]; 將會消耗大量的資源(而且大部分資源我們并不會使用),這是我們不愿意接受的.
            之所以出現(xiàn)這種情況,因為我們在構(gòu)造函數(shù)中分配了大量資源,這種分配是隨構(gòu)造函數(shù)的調(diào)用而自動完成的.


            這時,如果我們使用"兩段構(gòu)造"的方法就能在一定的程度上解決這個問題.

            class Server
            {
                
            public:
                    Server(
            void)
                    
            {
                        
            // do nothing here.
                    }

                    
                    
            bool Initialize( void )
                    
            {
                        
            // allocate a huge chunk of memory to store data
                    }

                    
                    
            ~Server( void )
                    
            {
                        
            // free all the used resource
                    }

            }
            ;


            在這種情況下: Server serverPool[ 100 ]; 的開銷就很小了,我們可以很好地控制對系統(tǒng)資源的使用,而不會浪費.
            當(dāng)然,當(dāng)我們從 serverPool 中獲取一個 Server 對象時,我們要調(diào)用 Initialize 進行最終的初始化操作.


            第三種方案:使用異常
            即是當(dāng)用于構(gòu)造 People 對象的參數(shù)非法時,我們選擇在構(gòu)造函數(shù)中拋出一個異常來反饋給調(diào)用者"參數(shù)非法,構(gòu)造失敗"的相關(guān)信息.

            class People
            {
                
            public:
                    
            explicit People( int iAge ) throw( std::invalid_arguement )
                    : m_iAge( iAge )
                    
            {
                        
            if( ( iAge < 0 ) || ( iAge > 200 ) )
                        
            {
                            
            throw std::invalid_arguement( "invalid argument" );
                        }

                    }

            }
            ;


            那么我們可以這樣使用:

            try
            {
                People hebe( 
            20 );
                hebe.Sing();
            }

            catchconst std::invalid_arguement &refExcept )
            {
                
            // handle exception here.
            }


            這種方案似乎是最優(yōu)的:符合RAII原則,也符合B.S等一批老大推行的"現(xiàn)代C++程序設(shè)計風(fēng)格".

            但是很多在開發(fā)一線上的同學(xué)們都反對在代碼中使用異常,實際上我也不愿意在代碼中使用異常,
            至少不愿意看到類似于java代碼中那樣鋪天蓋地的"throw try catch".
            我對異常的使用也僅僅是局限在類似于那些適合"用異常來代替兩段構(gòu)造"的場景中,對于其它的情況,
            我更愿意用返回錯誤碼來標(biāo)識函數(shù)內(nèi)部的運行狀態(tài),而不是通過拋出異常的形式來告知調(diào)用者.


            C++規(guī)定:如果執(zhí)行構(gòu)造函數(shù)的過程中產(chǎn)生異常,那么這個未被成功構(gòu)造的對象的析構(gòu)函數(shù)將不會被調(diào)用.
            這一點在很大程度上為我們在構(gòu)造函數(shù)中拋出異常的安全性提供了C++語言級的保證,當(dāng)然,其它的安全性需要我們自己保證.

            對于"向調(diào)用者反饋構(gòu)造函數(shù)參數(shù)非法或構(gòu)造失敗的相關(guān)信息"這個問題,"基于異常"和"基于兩段構(gòu)造"這兩種方案我都使用過一段時間,
            目的是確定對于自己而言到底哪一種方案用起來更舒服更適合自己.最終的結(jié)果是我選擇了"基于異常"這種形式.

            對于"基于異常"和"基于兩段構(gòu)造",沒有哪一種能在所有的情況下都是最優(yōu)的解決方案,印證了那句名言"there is no silver bullet".
            如何在不同的場景中選擇其一作為最優(yōu)的解決方案是我們在設(shè)計時需要權(quán)衡的問題.


            個人愚見,錯漏之處還請指正,歡迎大家踴躍發(fā)言:)

            posted on 2010-03-04 21:23 luckycat 閱讀(2755) 評論(18)  編輯 收藏 引用 所屬分類: C++

            評論:
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 22:03 | Corner Zhang
            呵呵!如果你要傳統(tǒng)的編程風(fēng)格,就用Initialize() Shutdown()的“兩段構(gòu)造”;若要趨向于捕捉異常的try catch的風(fēng)格,就用構(gòu)造器上的異常處理唄!

            具體取舍,看項目人員本身素質(zhì),按我的經(jīng)驗,傳統(tǒng)風(fēng)格的代碼易于調(diào)試跟蹤,錯誤就在附近。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 22:22 | qiaojie
            兩種方法都是錯誤的用法,這地方應(yīng)該使用斷言而不是異?;蛘咤e誤返回值。傳入錯誤的參數(shù)值是程序的BUG而不是真正的異常,異常是由于外部運行環(huán)境的錯誤引起而非程序本身的BUG(例如,內(nèi)存耗盡,網(wǎng)絡(luò)錯誤,文件錯誤等等),異常處理只應(yīng)該用在真正屬于異常的情況。當(dāng)然,有的時候People的age參數(shù)來自于用戶的輸入,這個時候也不應(yīng)該使用異常,而是在用戶輸入完成,對話框結(jié)束,構(gòu)造People之前,加入一個ValidateUserInput()函數(shù)來校驗用戶輸入,如果age屬于非法值,彈出一個錯誤對話框向用戶說明錯誤的原因。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 22:32 | luckycat
            @Corner Zhang
            關(guān)于這一點,我們的觀點還是很相近的,不同的編程風(fēng)格決定了不同的設(shè)計取舍以及代碼風(fēng)格,
            沒有哪一種一直都是最優(yōu)的,選擇一種適合自己的并一直堅持下去(當(dāng)然,適當(dāng)?shù)臅r候還是要變通一下).
            實際上我并不愿意在代碼中大量使用"throw try catch",所以,基本上我不愿意去看java代碼,
            就如你所說的,傳統(tǒng)的代碼風(fēng)格易于跟蹤調(diào)試;在我看來,傳統(tǒng)的"基于錯誤碼"的代碼比基于"使用異常"的代碼
            要緊湊得多,因為我們可以在錯誤發(fā)生的地方立即處理錯誤,而不像"基于異常"的代碼中我們要向下跨越N行代碼
            來進行錯誤處理(這一點使得代碼的可讀性很差).
            而且,如果try代碼塊中太大,那么在對應(yīng)的catch塊中盡管我們可以進行相應(yīng)的異常處理,但是此時我們卻失去了
            對發(fā)生錯誤的代碼上下文的必要了解.這一點使得我們降低了對代碼整體運行流程的可預(yù)知性,
            更重要的是也降低了錯誤處理的針對性,因為同一種類型的異??赡苡蓆ry代碼塊中的多個地方throw.具體是哪一個throw的無從了解.
            so,我的觀點是:讓構(gòu)造函數(shù)盡量簡單,減少誤用的可能性,并增加構(gòu)造函數(shù)的安全性(盡量減少構(gòu)造函數(shù)構(gòu)造失敗的可能性).
            這樣我們也就能在一定程度上減少對異常機制的依賴.至于其它的可帶有返回值的成員函數(shù)都使用"返回錯誤碼"來取代"拋出異常".
              回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 23:25 | luckycat
            @qiaojie
            呵呵,可不能一棒子打死啊.
            至于說,類似于"用戶輸入非法數(shù)據(jù)"之類的問題到底是算作錯誤還是異常情況,這一點依賴每個人對同一個事物的認(rèn)知,
            有的人認(rèn)為這是異常情況,有的人認(rèn)為這是錯誤,這個認(rèn)知層面上的問題我們先不討論,尊重每個人的看法.
            實際上,即使存在上面的認(rèn)知差異也沒有關(guān)系,因為問題的本質(zhì)是對"用戶輸入非法數(shù)據(jù)"這種異常也好,錯誤也好,
            我們在代碼邏輯中應(yīng)該如何處理,你覺得應(yīng)該用類似于"對話框+ValidateUserInput"之類的方法來處理,
            我覺得可以通過返回錯誤碼或拋出異常的形式來做處理. 本質(zhì)上都是在處理一種"非正常情況",只是我們的處理方式不同,
            你說應(yīng)該用你的方法比較好,我覺得用我的方法處理也是可行的,到底用哪一種呢,
            即使在這種情況下,我們還是很難選擇一個所有人都接受的處理方式. 這里就涉及到設(shè)計的權(quán)衡和取舍了,有很多種方法都可行,我們尊重每個人在特定的環(huán)境中所做出的選擇.

            /*
            "而是在用戶輸入完成,對話框結(jié)束,構(gòu)造People之前,加入一個ValidateUserInput()函數(shù)來校驗用戶輸入,
            如果age屬于非法值,彈出一個錯誤對話框向用戶說明錯誤的原因"
            */
            你這里只是對一種特例的處理,實際上我們很難在所有的情況都保證傳入構(gòu)造函數(shù)的參數(shù)是合法的,要是我們真的找到了這樣一種方法,
            那么"there is a silver bullet !" , 接下來,對于所有奮斗在開發(fā)一線的同學(xué)們而言,生活就要美好很多,應(yīng)該再也不會發(fā)生類似于"小貝"的悲劇了:)

            在你處理的特例中,既然我們能夠保證傳入構(gòu)造函數(shù)的參數(shù)一定是合法的,那確實太好了,"使用異常"和"兩段構(gòu)造"都是多余的.
            對于那種我們不能確保傳入構(gòu)造函數(shù)的參數(shù)是一定是合法的情況,我們該選擇哪種處理方式呢,這是這篇文章討論的根本問題.
            如果因為構(gòu)造函數(shù)的參數(shù)不合法,或者因為其它的原因構(gòu)造失敗,最基本的一點,我們應(yīng)當(dāng)讓調(diào)用者知道這一情況,
            至于調(diào)用者如何處理就不在我們關(guān)心的范圍之內(nèi)了,是"彈出對話框告知用戶重試","忽略這個錯誤",還是直接"abort",不同的場景下也有不用的選擇.
            我們要做的就是在"構(gòu)造一個對象發(fā)生異常時"告知調(diào)用者"發(fā)生了非正常情況".

            這篇文章的主題也就是討論"在構(gòu)造發(fā)生非正常情況時采取何種方式來告知調(diào)用者這一情況".
            對于這個問題很難有一個"放之四海而皆準(zhǔn)"的處理方案,因為這涉及到不同的編程風(fēng)格,應(yīng)用場景和設(shè)計時的取舍.

            不過我們還是可以踴躍地發(fā)表自己的看法,在討論和交流的過程中我們總能發(fā)現(xiàn)思維的閃光點.互相學(xué)習(xí):)  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 08:34 | 飯中淹
            我一般會用一種類似工廠的方法。

            class CPeople
            {

            int m_iAge;

            CPeople() : m_iAge(-1) {}
            ~CPeople() {}
            bool _Init( int iAge )
            {
            if( iAge <= 0 || iAge > 200 )return false;
            m_iAge = iAge;
            return true;
            }
            public:
            static CPeople * Create( int iAge )
            {
            CPeople * people = new CPeople();
            if( people != NULL &&
            !people->_Init(iAge) )
            {
            people->Release();
            people = NULL;
            }
            return people;
            }
            void Release() { delete this;}
            };


            私有的構(gòu)造函數(shù)和西狗函數(shù)確保他們不能被單獨分配和刪除
            所有的途徑只有create和release。

              回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 09:04 | LOGOS
            @飯中淹
            agree
            這正是我想說的。另外,在一些情形下構(gòu)造函數(shù)不易調(diào)試,而兩段構(gòu)造則能避開這一調(diào)試,選擇更好的調(diào)試的init  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 10:31 | 肥仔
            @飯中淹
            每個對象都要顯示的create和release。那基本上等價于放棄了C++構(gòu)造函數(shù)和析構(gòu)函數(shù)這兩個特性。
            對于棧對象,這樣會很累,也很容易泄漏。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 12:02 | luckycat
            @飯中淹
            將"兩段構(gòu)造"與"Fatcory Pattern"結(jié)合起來確實是一種巧妙的設(shè)計!
            內(nèi)部實現(xiàn)上還是"兩段構(gòu)造",但是對于 class 的用戶而言,class CPeople 展現(xiàn)的卻是一個單一的"構(gòu)造接口",
            用戶一旦調(diào)用這個接口"構(gòu)造對象",那么"兩段構(gòu)造"自動完成,極大地減少了"兩段構(gòu)造"中因為忘記調(diào)用"Initialize"所帶來的問題.
            class CPeople 中的 Create 和 Release 所扮演的角色類似于"構(gòu)造函數(shù)和析構(gòu)函數(shù)",都是進行資源的分配與回收操作.
            單純從"資源管理"的角度來說,肯定是"構(gòu)造函數(shù)和析構(gòu)函數(shù)"相比如"Create 和 Release"更優(yōu)一些,
            因為"構(gòu)造函數(shù)和析構(gòu)函數(shù)"對于"非動態(tài)分配的對象以及非placement new方式生成的對象",
            構(gòu)造和析構(gòu)都會由編譯器保證正確自動地調(diào)用,大大簡化了對資源的管理,或許這也是C++設(shè)計構(gòu)造和析構(gòu)的出發(fā)點之一.

            在"兩段構(gòu)造" & "Fatcory Pattern"這種模式下,所有的CPeople對象將都由 Create 接口創(chuàng)建,這勢必需要我們管理大量的動態(tài)分配的對象,
            在這種情況下,如果稍有不慎,我們將面臨"resource leak"的問題.這個時候如果我們能將動態(tài)分配的CPeople對象用一種更方便安全的方式來管理就更好了,
            于是我想到了boost::shared_ptr,不知道大家想到了什么?
            類似于下面這樣:

            void FreeResource( CPeople *pPeople )
            {
            if( NULL != pPeople )
            {
            pPeople -> Release();
            }
            }

            CPeople *pHebe = CPeople::Create( 2 );
            if( NULL == pHebe )
            {
            // handle error
            }

            boost::shared_ptr< CPeople > pPeople( pHebe , FreeResource );

            下面我們就可以使用 pPeople 這個智能指針"do whatever you want" :) ,而且使用起來直觀方便:
            pPeople -> Sing();
            也減少了對動態(tài)分配資源進行管理的復(fù)雜度.



              回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 17:36 | 望見
            為了程序的健壯性,多余的操作也是必須的。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 18:00 | qiaojie
            @luckycat
            你的根本問題在于你沒理解異常的使用哲學(xué),在錯誤的前提下去談什么設(shè)計的選擇根本就是一件毫無意義的事情。你如果要堅持自己的錯誤認(rèn)識,去談什么尊重每個人的選擇,那我只能自認(rèn)是對牛彈琴了。

            再來看為什么我說要用ValidateUserInput而不是其他什么方法,因為驗證用戶的輸入通常來說都是一個復(fù)雜的且經(jīng)??勺兊男枨螅覀兗僭O(shè)Person的構(gòu)造參數(shù)里有一個UserName,通常UserName的驗證會比較復(fù)雜,有長度限制,非法字符限制,重名限制等等,對于重名限制往往還要去用戶管理器或者后臺數(shù)據(jù)庫查詢一下,現(xiàn)在來看看把這些驗證邏輯都寫到Person的構(gòu)造函數(shù)里是多么傻X的做法啊,首先,Person的構(gòu)造函數(shù)需要依賴用戶管理器或者后臺數(shù)據(jù)庫才能驗證UserName的合法性。其次,當(dāng)你的構(gòu)造函數(shù)返回了錯誤值或者異常的時候,外部的處理代碼卻根本不知道為什么這個用戶名是非法的,所以要么還得再寫一遍驗證邏輯來檢查哪里非法,要么直接告訴用戶輸入非法,讓用戶摸不著頭腦。
              回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 19:33 | luckycat
            @qiaojie
            也許你的異常哲學(xué)是正確的并值得大家學(xué)習(xí),還請你發(fā)文一篇讓大家有一個學(xué)習(xí)的機會,
            如果從你的文章中我確實發(fā)現(xiàn)了自己的錯誤,也會從中有所改正,當(dāng)然,你也不需要"對牛彈琴"這個詞語.
            我這篇文章中的"People"只是一個用于作為討論基礎(chǔ)的例子,根本的問題是對于"構(gòu)造函數(shù)的參數(shù)非法或是構(gòu)造失敗"時,我們應(yīng)當(dāng)如果告知調(diào)用者.
            我并沒有說一定要把參數(shù)的合法性全部放在構(gòu)造函數(shù)中完成,但是在構(gòu)造函數(shù)中檢查參數(shù)的合法性是應(yīng)該的,
            就像上面的同學(xué)說的"為了程序的健壯性,多余的操作也是必須的"。
            在這個例子中,你可以往自己熟悉的GUI方向進行特化,所以你可以使用"對話框"之類的工具來進行傳入構(gòu)造函數(shù)之前的參數(shù)合法性檢驗以及進行相關(guān)的錯誤處理,
            但是在那些"非GUI"的領(lǐng)域,在那些"我們不能確保傳入構(gòu)造函數(shù)的參數(shù)一定是合法的,不能保證構(gòu)造函數(shù)一定會構(gòu)造成功"的情況下,我們到底該如何處理,
            我考慮到可以使用"基于異常"或"基于兩段構(gòu)造的形式".
            C++提供的異常機制是一種工具,可以作為"函數(shù)內(nèi)部向函數(shù)的調(diào)用者傳遞函數(shù)內(nèi)部非正常運行狀態(tài)"的一種方法.
            就如同你說的"內(nèi)存耗盡,網(wǎng)絡(luò)錯誤,文件錯誤"這種情況下是異常,也許這種情況下我們應(yīng)當(dāng)使用"異常機制"(希望沒有理解錯).
            但是如果一個函數(shù)內(nèi)部可能出現(xiàn)"內(nèi)存耗盡"也會出現(xiàn)"參數(shù)非法的問題"(再重申一遍,我們不能永遠都保證傳入每一個函數(shù)的參數(shù)都是合法的).
            "內(nèi)存耗盡"這種情況我們使用異常,但是"參數(shù)非法問題"我們使用什么呢,
            按照你的看法,"參數(shù)非法"不屬于異常的范圍之內(nèi),我們不應(yīng)該使用"異常的形式",但我們還是要告知用戶"參數(shù)非法"的信息,
            假定這里我們"無法使用類似于彈出對話框的形式來告知用戶參數(shù)非法",那么我可以想到的告知調(diào)用者這一信息的方式是"使用錯誤碼",
            當(dāng)然,我們還可以選擇"errno"的形式.
            這樣一來,我們就面臨一個問題"一個函數(shù)會以異常和錯誤碼兩種方式來告知調(diào)用者相關(guān)的非正常運行信息",
            接下來,調(diào)用者就要同時使用"try catch"和檢查函數(shù)的錯誤碼兩種方式來檢查函數(shù)的運行狀態(tài),
            我覺得如果真的這樣設(shè)計函數(shù)的話,這就是一種很糟糕的設(shè)計,不知道你怎么認(rèn)為.

            在告知調(diào)用者一個函數(shù)內(nèi)部的"非正常狀態(tài)"時,我只會擇優(yōu)使用"錯誤碼"或"異常這兩種形式"之一,不會同時使用.
            基于這一點,如果我選擇"以錯誤碼的形式"來反饋給調(diào)用者,那么在函數(shù)內(nèi)部"網(wǎng)絡(luò)錯誤"時我也會使用錯誤碼來告知調(diào)用者(按你的看法,這種情況應(yīng)該使用異常),
            如果我選擇"基于異常"的形式,那對"參數(shù)非法"的信息我也會拋出"std::invalid_arguement".這是設(shè)計上的取舍產(chǎn)生的必然選擇.


            說到這里,不知道你對于作為std異常類型之一的"std::invalid_arguement"這個詞語有什么感想,
            我覺得你應(yīng)該向標(biāo)準(zhǔn)委員會指明"std::invalid_arguement"這個詞語,
            "從使用異常的哲學(xué)上的角度上來看這個概念是錯誤的,因為參數(shù)非法根本就不是異常,我們又怎么能因為參數(shù)的非法而throw std::invalid_arguement,
            這是在誤導(dǎo)廣大的std用戶,所以必須去掉".  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 21:56 | qiaojie
            @luckycat
            保證參數(shù)的正確性是調(diào)用者的責(zé)任,而不是被調(diào)用的函數(shù)的責(zé)任,如果你對此有疑問的話可以去閱讀一下契約式編程。當(dāng)然,我并沒有說不需要去檢查參數(shù)的合法性,而是強調(diào)應(yīng)該用assert(0 < age && age < 200);這樣的斷言來檢查參數(shù)合法性,這個為什么強調(diào)不要用異常或者錯誤返回值,是因為盲目的大量使用這類錯誤處理機制會導(dǎo)致整個項目變得混亂,如果任意一個帶參數(shù)的函數(shù)都會因為參數(shù)非法而拋異常,那么我在外面接到異常的時候會非常困惑,到底該如何正確處理那么多可能出現(xiàn)的異常?如果是用錯誤返回值的話就更麻煩,每個函數(shù)調(diào)用都要進行檢查,代碼及其繁瑣。錯誤處理機制是整個軟件設(shè)計的重要環(huán)節(jié),他不像接口設(shè)計那么顯而易見,所以在設(shè)計上更應(yīng)該小心規(guī)劃合理使用,在
            哪里會出異常,哪里該接異常應(yīng)該做到心中有數(shù)正確處理,否則就會陷入混亂。
            當(dāng)然,凡事無絕對,我說的這種使用方式并不適用于組件級或者系統(tǒng)級程序,系統(tǒng)級程序必須要采用防御性編程策略來檢測參數(shù)的合法性并向調(diào)用者報告錯誤(因為無法預(yù)期調(diào)用者如何調(diào)用函數(shù)),這常需要付出代價,常導(dǎo)致系統(tǒng)級提供的API接口跟應(yīng)用程序之間阻抗適配,需要在應(yīng)用程序內(nèi)進行適當(dāng)?shù)姆庋b。而你的例子顯然不屬于這類程序。

            另外你拿std::invalid_arguement出來我覺得毫無意義,C++標(biāo)準(zhǔn)里不成熟不完善甚至很爛的東西多了去,說上三天三夜也說不完。C++的異常機制尤其不完善,備受爭議。像std::invalid_arguement基本沒人會去用。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 22:40 | luckycat
            @qiaojie
            謝謝賜教!

            "像std::invalid_arguement基本沒人會去用"這句話說得有點絕對了,用的人應(yīng)該還是有一些的,可能我們沒有接觸到.
            另外,我們總是被"天朝"代表,想不到這次被 qiaojie 代表了:)

            你說"保證參數(shù)的正確性是調(diào)用者的責(zé)任,而不是被調(diào)用的函數(shù)的責(zé)任",
            這一點我也同意,不過我覺得作為函數(shù)的設(shè)計者,我們不應(yīng)當(dāng)對用戶所傳遞的參數(shù)有太多理想化的假設(shè),所以我們應(yīng)當(dāng)在函數(shù)中進行參數(shù)合法性的檢查,
            一方面,可以在函數(shù)的入口處盡早發(fā)現(xiàn)非法參數(shù)的問題,這樣就不至于后續(xù)會使用錯誤的參數(shù)在函數(shù)中進行一些無意義的操作.
            另一方面,在函數(shù)入口處檢查參數(shù)的合法性,可以增強函數(shù)的健壯性,進一步增強系統(tǒng)的健壯性.
            舉個例子,如果傳遞給函數(shù)的實參不應(yīng)該是NULL指針,用戶卻以NULL作為實參調(diào)用函數(shù),假設(shè)我們沒有進行對應(yīng)參數(shù)合法性檢查,
            那么后續(xù)基于這個NULL實參的操作可能會導(dǎo)致系統(tǒng)"coredump".

            對于參數(shù)的合法性檢查,在debug版本和release版本下應(yīng)該都需要進行,類似于"assert(0 < age && age < 200);"這種檢測參數(shù)的合法性的代碼只在debug版本下可以起作用,
            在release版本下就不起用了,也就不能在release版本下作為參數(shù)合法性檢查的工具.
            在debug版本下,如果assert斷言失敗,那么我們可以看到對應(yīng)的abort信息,然后程序異常退出.
            實際上這樣做可能有的時候并不合適,因為在一些情況下,僅僅是參數(shù)非法,我們可以進行相應(yīng)的處理而不需要系統(tǒng)因此而退出運行.

            "強調(diào)不要用異常或者錯誤返回值,是因為盲目的大量使用這類錯誤處理機制會導(dǎo)致整個項目變得混亂"
            這句話如果僅僅是理論上來探討"如何讓系統(tǒng)設(shè)計的更優(yōu)雅",那么這無疑可以作為一個"系統(tǒng)設(shè)計準(zhǔn)則",
            但是在實際的開發(fā)過程中,有的時候一個函數(shù)內(nèi)部出現(xiàn)"非正常情況"的可能性實在是太多了,我們必須要進行相應(yīng)的處理.
            如果我們既不使用"異常"也不使用"返回錯誤碼"的形式來告知調(diào)用者,
            那么在反饋給調(diào)用者"函數(shù)內(nèi)部出現(xiàn)非正常情況"這一點上我們將"無能為力",但我們又必須在這一點有所作為.

            在大多數(shù)情況下,"異常"和"錯誤碼"可能是我們僅有的兩個選擇方案,如何選擇其一作為最終的處理方案,
            甚至如何在不使用"異常"和"錯誤碼"的前提下也達到相同的效果,這是一件很"糾結(jié)"的事情.


            追求系統(tǒng)在架構(gòu)和代碼設(shè)計上的完美是開發(fā)者的一個方向,但是有時我們需要考慮"追求完美的代價",
            在時間,人力以及成本的多重影響下,很多時候我們必須放棄對最優(yōu)方案的探索,而選擇一種"不那么完美但是可行,可以很好解決問題"的方案.
            也許這個時候作為函數(shù)調(diào)用狀態(tài)反饋的"異常"和"錯誤碼"機制會在我們的思考和運用范圍之內(nèi).


              回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-06 08:55 | chentan
            qiaojie 討論很精彩
            我的習(xí)慣是 模塊邊界會檢查參數(shù)合法性,并報告給調(diào)用者
            模塊內(nèi)部的代碼盡量多的用assert  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-06 21:06 | 陳梓瀚(vczh)
            對于類庫的設(shè)計者,其實有一個很簡單的判斷標(biāo)準(zhǔn)。

            1:如果一個輸入錯誤,調(diào)用者必須知道并處理,那么就采用“不處理就崩潰”方法,迫使調(diào)用者處理。在C++里面的唯一方法就是使用異常了。舉個例子,對一個容器的下標(biāo)訪問越界,這個時候operator[]不可能返回一個可用的返回值,而且也不能用錯誤信息去污染返回值,譬如返回pair<bool, T>,因此直接拋出異常。

            2:如果一個構(gòu)造函數(shù)發(fā)生錯誤之后,這個對象是絕對不能被碰的,那么采用異常。因為在try-catch結(jié)構(gòu)里面,構(gòu)造函數(shù)完蛋了,那么那個正在被構(gòu)造的對象你是沒有辦法獲取的。

            3:異??梢杂脕碓诖罅亢瘮?shù)互相遞歸的時候(譬如說語法分析器)迅速跳到最外層,此處作為控制代碼流程而使用。

            這里我也接受一個例外,譬如說2,如果構(gòu)造函數(shù)發(fā)生錯誤但是我并不想立刻拋出異常(因為有些狀態(tài)在構(gòu)造函數(shù)里面想知道也比較麻煩),那么除了需要一個類似bool IsAvailable()const函數(shù)來告訴你以外,所有該類成員的非法操作(譬如說在錯誤的構(gòu)造狀態(tài)下調(diào)用一個成員函數(shù))都必須拋出異常,或者【絕對不產(chǎn)生任何副作用】的同時給一個返回值說明調(diào)用失敗。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-06 21:27 | luckycat
            @陳梓瀚(vczh)
            你列舉的判斷標(biāo)準(zhǔn)都值得借鑒,不過你后續(xù)補充的對"例外情況"的處理方式不敢茍同:
            因為構(gòu)造可能沒有成功,那么我們需要調(diào)用IsAvailable之類的函數(shù),甚至于后續(xù)需要因為判斷之前構(gòu)造函數(shù)的狀態(tài)來
            對調(diào)用的每個成員函數(shù)進行"try catch"或者還要從"每個成員函數(shù)的返回值中來判斷之前的構(gòu)造操作是否成功".
            這種設(shè)計是可行的,但對類的使用者來說太復(fù)雜.
            這種情況下我覺得使用"兩段構(gòu)造"可能更好一些,我們只需要判斷"兩段構(gòu)造"是否成功即可,如果構(gòu)造成功,在后續(xù)的成員函數(shù)調(diào)用過程中,
            就再也不用為了確認(rèn)構(gòu)造函數(shù)的狀態(tài)來對每個被調(diào)用的成員函數(shù)進行"try catch"或檢查返回值的操作,這樣的設(shè)計應(yīng)該更簡潔一些.  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2010-03-08 01:28 | 陳梓瀚(vczh)
            @luckycat
            兩段構(gòu)造的話,你的類的其他成員函數(shù)也應(yīng)該在構(gòu)造不成功的時候拋出異常,并沒有任何區(qū)別,我的例外還減少了別人可能忘記第二段構(gòu)造的概率。

            當(dāng)然這是對一個庫的要求,應(yīng)用程序不一定要如此嚴(yán)格。  回復(fù)  更多評論
              
            # re: 設(shè)計的兩難:選擇異常還是兩段構(gòu)造 2015-01-27 23:09 | tsgsz
            拋異常不析構(gòu)可以令所有的成員變量都是 std::unique_ptr  回復(fù)  更多評論
              
            久久亚洲中文字幕精品有坂深雪 | 久久只这里是精品66| 三级韩国一区久久二区综合| 久久精品人人做人人爽电影| 久久w5ww成w人免费| 26uuu久久五月天| 国产亚洲精久久久久久无码77777| 久久发布国产伦子伦精品| 国产激情久久久久影院老熟女免费 | 国产成人久久久精品二区三区| 久久久久亚洲AV成人网| 欧美黑人又粗又大久久久| 久久99国产综合精品| 欧美日韩成人精品久久久免费看| 婷婷五月深深久久精品| 婷婷久久综合九色综合绿巨人| 香蕉久久夜色精品国产尤物| 伊人丁香狠狠色综合久久| 亚洲国产美女精品久久久久∴ | 国内精品伊人久久久久777| 一级做a爱片久久毛片| 午夜人妻久久久久久久久| 久久精品国产精品亜洲毛片| 久久99热精品| 91久久精品91久久性色| 无码日韩人妻精品久久蜜桃| 久久久久久无码国产精品中文字幕 | 久久国产精品久久精品国产| 亚洲国产精品无码久久SM| 99久久这里只精品国产免费| AV无码久久久久不卡蜜桃| 亚洲AV日韩精品久久久久| 色偷偷88欧美精品久久久| 久久久久亚洲AV无码去区首| 日韩精品久久久久久| 少妇精品久久久一区二区三区 | 久久亚洲国产午夜精品理论片| 国内精品久久久久影院一蜜桃| 欧洲成人午夜精品无码区久久| 日产精品久久久久久久| 久久精品国产亚洲av麻豆色欲|