C++基本功和 Design Pattern系列(11) Exception

本文探讨了C++中的异常处理机制,包括返回值、断言、调试输出和异常处理等多种方式。重点介绍了异常类的定义与使用,构造函数、析构函数及运算符中的异常安全处理,并讨论了资源获取即初始化(RAII)技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

======================================================

大家请把我的文章当参考,详细内容 还请参照 权威书籍如果文中有错误和遗漏, 请指出,Aear会尽力更正, 谢谢!

 Aear Blog: http://blog.sina.com.cn/u/1261532101 ======================================================

说到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 _ptr1; auto_ptr _ptr2; } Destructor中不需要catch exception,因为destructor主要是调用其他的destructor,没有任何的destructor会throw exception的,所以没必要catch. 这次就说这么多,大家过的开心,下次见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值