C++基本功和 Design Pattern系列 Exception

來源:互聯網
上載者:User

說到Exception就要說下相關的Error Handling. 比較常用的Error Handling一般有如下幾種類方式:
    1. Return value
    2. Assert
    3. Debug Output
    4. Exception

相對於其他三種錯誤處理方式, Exception更加容易使用,而且使得錯誤碼相對集中,同時使得獨立函數庫的開發更加方便。同樣,對於C++來說, Exception提供了Class的Constructor 和 Operator = 錯誤處理機制,因為這兩者都不是能夠通過return value進行報錯的。

但是就遊戲開發來說, Exception最大的缺點是記憶體和CPU的開銷。當然,不是說遊戲的代碼中不應該使用Exception。 Aear見過用Exception的遊戲代碼,也有完全不用Exception的代碼。因為對遊戲來說,應該在運行過程中保持自身狀態的正確性,不應該產生任何的無法處理的Exception。而所有能夠自己處理的錯誤情況,都是能夠通過Return value 來解決的。唯一可能產生Exception的地方,就是系統資源,比如磁碟檔案,網路等。不過大部分系統掉用都提供非Exception的錯誤處理。不過程式開發各不相同,用不用Exception可能還是需要大家自行決定。

Aear個人觀點是能不用Exception,就不用Exception,但是應該用Exception的時候,一定不要省。比如constructor裡。

============ Exception的用法 ============

要使用Exception, 要麼用系統的Exception的類,要麼定義自己的類。在定義自己類的時候,可以繼承STD裡邊的Exception類,也可以建立自己新的類。比如:

class ExceptionBase{
    ...
};

class ExceptionDerived : public ExceptionBase{
    ...
};

需要注意的是,通常定義自己的Exception類的時候,都要有一個公用的Base Exception Class, 這樣能夠保證寫代碼的時候catch所有的你自訂的Exception,比如:

try {
    ...
}catch( ExceptionDerived & e ) {
    ...
}catch( ExceptionBase & e ) {

    // Catch 其他的Exception, 這樣的設計即使今後添加新的Exception,只要
    // 是從ExceptionBase繼承來的,都會被catch到。

}catch( ... ) {

    // 這裡最好再加上 catch(...)來catch所有的exception,防止有未catch的    // exception. 因為如果有unexpected exception, C++的預設動作是直接
    // 終止程式的運行。

};

============ Exception in Constructor ============

如果一個Constructor產生exception而且沒有被程式catch到的話,那麼這個object的建立就會失敗, 比如:

class MemoryBlock {
private:
    void * _pMem;
public:
    MemoryBlock ( UINT32 size )
    {
       _pMem = new char[size];
    };
    ....
};

MemoryBlock myMemory(100000000000000000000000000);

如果new在分配記憶體的過程中throw一個Exception ,通常是 bad_alloc,那麼myMemory的建立就會失敗,以後任何對 myMemory的成員訪問,都是非法的,會導致程式的崩潰。

讓我們看看另一中寫法:

class MemoryBlock {
private:
    void * _pMem;
public:
    MemoryBlock ( UINT32 size ) :
       _pMem(new char[size])
    { };
    ....
};
上面也是合法的,不過會產生同樣的問題。但是區別在於如果在代碼中catch到exception,那麼第一種寫法,能夠保證object被建立,而第二種寫法不能。比如:

    // MemoryBlock 能夠被建立
    MemoryBlock ( UINT32 size )
    {
       try {
           _pMem = new char[size];
       } catch(...) {}
    };

    // MemoryBlock 建立失敗
    MemoryBlock ( UINT32 size )
    try
       : _pMem(new char[size])
    { } catch(...) {};

============ Exception in Destructor ============

其實對於Destructor來說就一句話,不能在Destructor中Throw Exception。原因很簡單,因為通常Destructor要麼在Delete Object中掉用,要麼在已經Throw了Exception的時候,由系統掉用。如果在Throw Exception的情況下再Throw Exception的話,那麼程式就會強制終止。

============ Exception in Operator ============

這個是比較麻煩的,通常的Exception的處理有好幾個層級, Basic, Strong, Nofail.我們這裡只說下Strong Exception Safety。 下面是個例子:

class X {
    ...
private:
    void * _pMem1;
    UINT32 _pMemSize1;
    void * _pMem2;
    UINT32 _pMemSize2;

public:
    X& operator = ( const X & xo )
    {
        if( _pMem1 ) delete _pMem1;
        if( _pMem2 ) delete _pMem2;
        
        _pMem1 = new char[xo._pMemSize1];
        _pMem2 = new char[xo._pMemSize1];
        ...
    };
};

這裡如果 _pMem2 = new char[xo._pMemSize1]; Throw一個Exception,那麼X只是被Copy了一半。狀態是不完整的。但是原來在pMem1&2中的資料已經消失了。如果是Strong Exception Safety,那麼要求如果throw excpetion,那麼class的資料應該恢複在之前的狀態,比如經典的exception safe operator = 如下:

    X& operator = ( const X & xo )
    {
        X temp(xo);
        swap( *this, temp );
        return *this;
    };

swap是交換*this 和 Temp的所有資料。通常我們能夠保證這個過程沒有任何exception的產生。因此即使 temp(xo) throw一個exception, 也不會影響當前類的任何狀態變化。

============ RAII ============

最後說一種不使用Exception而能保證沒有Resource Leakage的技術。那就是 Resource Aquisition Is Initialization ( RAII ). 其原理很簡單,就是C++標準保證一個被成功建立的 Object,無論任何情況下(即使是在Throw exception ), 它的 Destructor都會被掉用。 因此,我們可以用一個object 的constructor 來擷取資源,用Destructor來釋放資源。下面舉個最簡單的應用,thread 的 asynchronization:

class CriticalSection {
public:
    CriticalSection( CRTICIAL_SECTION *pCs ) :
       _pCs(pCS)
    { EnterCriticalSection( _pCS ) };

    ~CriticalSection( )
    { LeaveCriticalSection( _pCS ) };
private:
    CRTICIAL_SECTION * _pCs;
};

通常我們使用Critical Section的時候,用下列方式:

void threadXX( CRTICIAL_SECTION * pCs)
{
    EnterCriticalSection( pCS );

    void * pTemp = new char[100000000];

    LeaveCriticalSection( pCS );
}

問題是如果     void * pTemp = new char[100000000]; Throw一個 bad_alloc,那麼 LeaveCriticalSection( pCS );就不會被掉用而直接返回,很容易導致死結。類似的代碼在遊戲伺服器端的設計是很常見的,正確的做法是使用上面定義的類:

void threadXX( CRTICIAL_SECTION * pCs)
{
    CriticalSection temp( pCS );

    void * pTemp = new char[100000000];
}

由於即使throw exception, C++保證temp的destructor一定會被調用。因此不會產生死結的情況。

============ 其他 ============

比如下面的代碼是很容易產生問題的:
    function( new char[100], new char[300] );
如果new char[300]throw exception,那麼 new char[100]很有可能就不會被釋放。

推薦使用auto_ptr或者boost中的Shared_ptr,特別是在class 的initialization list 中, 比如下列做法不使用catch exception也不會產生記憶體泄露:

class X{
    X() :
    _ptr1(new XXX() ),
    _ptr2(new XXX() )
    {};

private:
    auto_ptr<void *> _ptr1;
    auto_ptr<void *> _ptr2;
}

Destructor中不需要catch exception,因為destructor主要是調用其他的destructor,沒有任何的destructor會throw exception的,所以沒必要catch.

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.