Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1039601
  • 博文数量: 243
  • 博客积分: 3053
  • 博客等级: 中校
  • 技术积分: 2975
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-02 21:11
文章分类

全部博文(243)

文章存档

2013年(2)

2012年(20)

2011年(5)

2010年(114)

2009年(102)

我的朋友

分类:

2010-11-22 14:54:08

1   为什么需要异常
《Windows核心编程》第4版第13章开头部分描述了一个美好世界,即所编写的代码永远不会执行失败,总是有足够的内存,不存在无效的指针。但是,那是不存在的世界,于是,我们需要有一种异常的处理措施,在C语言中最常用的(其实C++中目前最常用的还是)是利用错误代码(Error Code)的形式。这里也为了更好的说明,也展示一下Error Code的示例代码:
Error Code常用方式:
    1.最常用的就是通过返回值判断了:比如C Runtime Library中的fopen接口,一旦返回NULL,Win32 API中的CreateFiley一旦返回INVALID_HANDLE_VALUE,就表示执行失败了。
    2.当返回值不够用(或者携带具体错误信息不够的)时候,C语言中也常常通过一个全局的错误变量来表示错误。
    比如C Runtime Library中的errno 全局变量,Win32 API中的GetLastError,WinSock中的WSAGetLastError函数就是这种实现。
    既然Error Code在这么久的时间中都是可用的,好用的,为什么我们还需要其他东西呢?
    这里可以参考一篇比较浅的文章。《错误处理和异常处理,你用哪一个》,然后本人比较钦佩的pongba还有一篇比较深的文章:《错误处理(Error-Handling):为何、何时、如何(rev#2)》,看了后你一定会大有收获。当pongba列出了16条使用异常的好处后,我都感觉不到我还有必要再去告诉你为什么我们要使用异常了。
    但是,这里在其无法使用异常的意外情况下,(实际是《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》一书中所写)
    一,用异常没有带来明显的好处的时候:比如所有的错 误都会在立即调用端解决掉或者在非常接近立即调用端的地方解决掉。
    二,在实际作了测定之后发现异常的抛出和捕获导致了显著的时间开销:这通常只有两种情 况,要么是在内层循环里面,要么是因为被抛出的异常根本不对应于一个错误。
   三,控制程序流的返回值不是错误,如果一个情况经常发生,往往意味着它是用来控制程序流程的,应该用
status-code返回(注意,不同于error-code),比如经典的while(cin >> i)读入整型失败是很常见的情况,而且,这里的“读入整型失败”其实真正的含义是“流的下一个字段不是整型”,后者很明确地不代表一个错误;再比如在一个字符串中寻找一个子串,如果找不到该子串,也不算错误。这类控制程序流的返回值都有一个共同的特点,即我们都一定会利用它们的返回值来编写if-else,循环等控制结构。
   四,编程bug不是错误。属于同一个人维护的代码,或者同一个小组维护的代码,如果里面出现bug,使得一个函数的前提条件得不到满足,那么不应该视为错误。而应该用assert来对付。因为编程bug发生时,你不会希望栈回滚,而是希望程序在assert失败点上直接中断,调用调试程序,查看中断点的程序状态,从而解决代码中的bug

   五,频繁出现的不是错误。频繁出现的情况有两种可能,一是你的程序问题大了(不然怎么总是出错呢?)。二是出现的根本不是错误,而是属于程序的正常流程。后者应该改用status-code
 
    很明显,文中列举的都是完全理论上理想的情况,受制于国内的开发环境,无论多么好的东西也不一定实用,你能说国内多少地方真的用上了敏捷开发的实践经验?这里作为现实考虑,补充几个没有办法使用异常的情况:
    一.所在的项目组中没有合理的使用RAII的习惯及其机制,比如无法使用足够多的smart_ptr时,最好不要使用异常,因为异常和RAII的用异常不用RAII就像吃菜不放盐一样。这一点在后面论述一下。
    二.当项目组中没有使用并捕获异常的习惯时,当项目组中认为使用异常是奇技淫巧时不要使用异常。不然,你自认为很好的代码,会在别人眼里不可理解并且作为异类,接受现实。
2 基础篇
   先回顾一下标准C++的异常用法
   1.C++标准异常只有一种语法,格式类似:
    try
    {
    }
    catch()
    {
    }
    经常简写为try-catch,当然,也许还要算上throw。格式足够的简单。
   这里可以体现几个异常的优势,比如自己的异常继承体系,携带足够多的信息等等。
    另外,虽然在基础篇,这里也讲讲C++中异常的语义,
    如下例子中,
    throwException
    #include 
     #include 
     using namespace std;

    class MyException : public exception
    {
    public:
        MyException(const char* astrDesc)
        {
           mstrDesc = astrDesc;
        }
         MyException(const MyException& aoOrig)
        {
           cout <<"Copy Constructor MyException" <           mstrDesc = aoOrig.mstrDesc;
        }

        MyException& operator=(const MyException& aoOrig)
        {
           cout <<"Copy Operator MyException" <           if(&aoOrig == this)
           {
               return *this;
           } 
           mstrDesc = aoOrig.mstrDesc;
           return *this;
         }
 
        ~MyException()
        {
           cout <<"~MyException" <        }
        string mstrDesc;
    };
 
    void exceptionFun()
    {
        try
        {
           throw MyException("A My Exception");
        }
        catch(MyException e)
       {
          cout <           e.mstrDesc = "Changed exception.";
           throw;
        }
    }
 
    int _tmain(int argc, _TCHAR* argv[])
    {
        try
        {
           exceptionFun();
        }
        catch(MyException e)
       {
           cout <           throw;
        }
        return 0;
     }
 
输出:
Copy Constructor MyException
A My Exception In exceptionFun.
~MyException
Copy Constructor MyException
A My Exception Out exceptionFun.
~MyException

  可以看出当抛出C++异常的copy语义,抛出异常后调用了Copy Constructor,用新建的异常对象传入catch中处理,所以在函数中改变了此异常对象后,再次抛出原异常,并不改变原有异常。这里我们经过一点小小的更改,看看会发生什么:

   throwException

    #include 
     #include 
     using namespace std;

    class MyException : public exception
    {
    public:
        MyException(const char* astrDesc)
        {
           mstrDesc = astrDesc;
        }
         MyException(const MyException& aoOrig)
        {
           cout <<"Copy Constructor MyException" <           mstrDesc = aoOrig.mstrDesc;
        }

        MyException& operator=(const MyException& aoOrig)
        {
           cout <<"Copy Operator MyException" <           if(&aoOrig == this)
           {
               return *this;
           } 
           mstrDesc = aoOrig.mstrDesc;
           return *this;
         }
 
        ~MyException()
        {
           cout <<"~MyException" <        }
        string mstrDesc;
    };
 
    void exceptionFun()
    {
        try
        {
           throw MyException("A My Exception");
        }
        catch(MyException e)
       {
          cout <           e.mstrDesc = "Changed exception.";
           throw e;
        }
    }
 
    int _tmain(int argc, _TCHAR* argv[])
    {
        try
        {
           exceptionFun();
        }
        catch(MyException e)
       {
           cout <           throw;
        }
        return 0;
     }
 

  这里和throwException程序的唯一区别就在于不是抛出原有异常,而是抛出改变后的异常,输出如下:

Copy Constructor MyException
A My Exception In exceptionFun.
Copy Constructor MyException
Copy Constructor MyException
~MyException
~MyException
Changed exception. Out exceptionFun.
~MyException

  你会发现连续的两次Copy Constructor都是改变后的异常对象,这点很不可理解。。。。。。。因为事实上一次就够了。但是理解C++的Copy异常处理语义就好理解了,一次是用于传入下一次的catch语句中的,还有一次是留下来,当在外层catch再次throw时,已经抛出的是改变过的异常对象了,我用以下例子来验证这点:

  throwTwiceException

#include 
#include 
using namespace std;
 
class MyException : public exception
{
public:
    MyException(const char* astrDesc)
    {
       mstrDesc = astrDesc;
    }
 
    MyException(const MyException& aoOrig)
    {
       cout <<"Copy Constructor MyException" <       mstrDesc = aoOrig.mstrDesc;
    }
 
    MyException& operator=(const MyException& aoOrig)
    {
       cout <<"Copy Operator MyException" <       if(&aoOrig == this)
       {
           return *this;
       }
 
       mstrDesc = aoOrig.mstrDesc;
       return *this;
    }
 
    ~MyException()
    {
       cout <<"~MyException" <    }
 
 
    string mstrDesc;
};
 
void exceptionFun()
{
    try
    {
       throw MyException("A My Exception");
    }
    catch(MyException e)
    {
       cout <       e.mstrDesc = "Changed exception.";
       throw e;
    }
}
 
void exceptionFun2()
{
    try
    {
       exceptionFun();
    }
    catch(MyException e)
    {
       cout <       throw;
    }
 
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
       exceptionFun2();
    }
    catch(MyException e)
    {
       cout <       throw;
    }
 
 
    return 0;
}
 

输出如下,印证了我上面的说明。

Copy Constructor MyException
A My Exception In exceptionFun.
Copy Constructor MyException
Copy Constructor MyException
~MyException
~MyException
Changed exception. In exceptionFun2.
~MyException
Copy Constructor MyException
Changed exception. Out exceptionFuns.

  上面像语言律师一样的讨论着C++本来已经足够简单的异常语法,其实简而言之,C++总是保持着一个上次抛出的异常用于用户再次抛出,并copy一份在catch中给用户使用。

  但是,实际上,会发现,其实原有的异常对象是一直向上传递的,只要你不再次抛出其他异常,真正发生复制的地方在于你catch异常的时候,这样,当catch时使用引用方式,那么就可以避免这样的复制。

  referenceCatch

#include 
#include 
using namespace std;
 
class MyException : public exception
{
public:
    MyException(const char* astrDesc)
    {
       mstrDesc = astrDesc;
    }
 
    MyException(const MyException& aoOrig)
    {
       cout <<"Copy Constructor MyException: " <       mstrDesc = aoOrig.mstrDesc;
    }
 
    MyException& operator=(const MyException& aoOrig)
    {
       cout <<"Copy Operator MyException:" <       if(&aoOrig == this)
       {
           return *this;
       }
 
       mstrDesc = aoOrig.mstrDesc;
       return *this;
    }
 
    ~MyException()
    {
       cout <<"~MyException" <    }
 
 
    string mstrDesc;
};
 
void exceptionFun()
{
    try
    {
       throw MyException("A My Exception");
    }
    catch(MyException& e)
    {
       cout <       e.mstrDesc = "Changed exception.";
       throw;
    }
}
 
void exceptionFun2()
{
    try
    {
       exceptionFun();
    }
    catch(MyException e)
    {
       cout <       throw;
    }
 
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
       exceptionFun2();
    }
    catch(MyException e)
    {
       cout <       throw;
    }
 
 
    return 0;
}

  上例中,使用引用方式来捕获异常,输出如下:

A My Exception In exceptionFun.
Copy Constructor MyException: Changed exception.
Changed exception. In exceptionFun2.
~MyException
Copy Constructor MyException: Changed exception.
Changed exception. Out exceptionFuns.
~MyException

  完全符合C++的引用语义。

  基本可以发现,做了很多无用功,因为try-catch无非是一层迷雾,其实这里复制和引用都还是遵循着原来的C++简单的复制,引用语义,仅仅这一层迷雾,让我们看不清楚原来的东西。所以,很容易理解一个地方throw一个对象,另外一个地方catch一个对象一定是同一个对象,其实不然,是否是原来那个对象在于你传递的方式,这就像这是个参数,通过catch函数传递进来一样,你用的是传值方式,自然是通过了复制,通过传址方式,自然是原有对象,仅此而已。

  另外,最终总结一下,《C++ Coding  Standards》73条建议Throw by value,catch by reference就是因为本文描述的C++的异常特性如此,所以才有此建议,并且,其补上了一句,重复提交异常的时候用throw;

阅读(684) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~