[C++] Exception

1 例子: throw, try, catch

当抛出(throw)对象时,异常被引发(raise)。

// throws exception if both objects do not refer to the same isbn
Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs)
{
    if (!lhs.same_isbn(rhs))
        throw runtime_error("Data must refer to same ISBN");
    //ok, if we're still here the ISBNs are the same so it's okay to do the addition
    Sales_item ret(lhs); // copy lhs into a local object that we'll return
    ret += rhs;          // add in the contents of rhs
    return ret;          // return a copy of ret
}
// part of the application that interacts with the user
 Sales_item item1, item2, sum;
 while (cin >> item1 >> item2) {  // read two transactions
     try {
         sum = item1 + item2;     // calculate their sum
     } catch (const runtime_error &e) {
       cerr << e.what() << " Try again.\n" << endl;
     }
 }

函数可能抛出异常,需要检查的代码放在try语句块中,在catch语句块中处理捕捉到的异常。

2 重新抛出异常

如果有异常 catch 无法处理,那么它使用空的 throw 语句将异常沿函数调用链向上重新抛出:

throw;

例子:

catch (my_error &eObj) {   // specifier is a reference type
    eObj.status = severeErr;  // modifies the exception object
    throw; // the status member of the exception object is severeErr
} catch (other_error eObj) {   // specifier is a nonreference type
    eObj.status = badErr;   // modifies local copy only
    throw; //the status member of the exception rethrown is unchanged
}

3 捕捉所有异常

使用 catch(...) 捕捉所有异常,如果和其他catch一起使用,catch(...)要放在最后。

int manip() {
   try {
       // actions that cause an exception to be thrown
   }
   catch (const Exception& e) {
       // actions
   }
   catch (...) {
   }
}

4 C++ 中列举的异常

namespace std {
    class bad_cast;
    class bad_alloc;
    class logic_error;
        class domain_error;
        class invalid_argument;
        class length_error;
        class out_of_range;
    class runtime_error;
        class range_error;
        class overflow_error;
        class underflow_error;
}

5 除零异常

问题:

如下的代码试图捕捉除0异常,看似合理,但实际上无法捕捉到。

int i = 0;
cin >> i;  // what if someone enters zero?
try {
    i = 5/i;   // 除0不是C++异常!!! 
}
catch (std::logic_error e) {
    cerr << e.what();
}

原因:

整数除0不属于C++异常, 要自行检查代码。
浮点除0也不是,但至少还有处理这种异常的具体方式。
你可能会想,用 overflow_error 就可以捕捉了,但实际上:

C++11 第 5.6 章明确声明:
如果 / 或 % 的第二个操作数是0, 行为未定义。

即: 它有可能 会抛出异常。 它也有可能格式化你的硬盘并嘲笑你。

捕捉除0异常的示例code:

#include <iostream>
#include <stdexcept>
// Integer division, catching divide by zero.
inline int intDivEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator / denominator;
}
int main (void) {
    int i = 42;

    try {
        i = intDivEx (10, 2);
    } catch (std::overflow_error e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;

    try {
        i = intDivEx (10, 0);
    } catch (std::overflow_error e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;

    return 0;
}
//以上代码输出:
//5
//Divide by zero exception -> 5

运算符 % 也一样:

// Integer remainder, catching divide by zero.
inline int intModEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator % denominator;
}

6 栈展开(Stack Unwinding)

当异常被抛出后,当前函数的执行被挂起,随后开始查找匹配的catch 。查找过程首先检查 throw是否位于try 语句块内,如果是这样,与该try 关联的catch 语句被逐一比对是否有一个与被抛出的对象匹配。如果找到匹配的catch, 异常被处理,如果没有找到,当前函数退出,内存释放,局部对象被销毁,查找过程在上一级调用函数中继续进行。

如果对抛出异常函数的调用在一个 try语句内部,则会检查与之关联的catch 语句,如果找到匹配的catch ,异常被处理。如果没找到,这个调用函数也将退出,查找在上一个调用此函数的函数中继续进行。

此过程称为栈展开,沿嵌套的函数调用链向上,直到找到匹配的catch 。一旦找到匹配的 catch, 就会进入那一个catch, 执行在此 handler中继续。当此catch结束后, 执行从对应try的最后一个catch语句后开始执行。

异常必须处理,否则程序将调用库函数 terminate 终止执行。

析构函数中永远不要 throw 异常

原因:当局部类对象作用域结束,或者对象创建后,发生异常,函数提前退出,析构函数会执行资源释放等操作。如果析构函数中再次抛出异常,难以想象处理逻辑。
所有标准库类型的析构函数都承诺不会引发(raise)异常。

7 std::auto_ptr

如下的例子,如果 x != 0, 将产生内存泄露:

void func( int x )
{
    char* pleak = new char[1024]; // might be lost => memory leak
    std::string s( "hello world" ); // will be properly destructed
    if ( x ) throw std::runtime_error( "boom" );
    delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
    try {
        func( 10 );
    }
    catch (const std::exception& e) {
        return 1;
    }
    return 0;
}

解决方法之一,是将动态分配的变量设计成类的对象,即使出现异常,自动调用的析构函数也能释放内存。
另一个方法是使用标准库中的智能指针,例如使用类std::auto_ptr:

对比:

void f()
{
    int *ip = new int(42);     // dynamically allocate a new object
    // code that throws an exception that is not caught inside f
    delete ip;    // return the memory before exiting
}

以上代码,如果 delete ip之前出现异常,将产生内存泄露。下面的代码,使用std::auto_ptr则能避免此问题。

void f()
{
   auto_ptr<int> ap(new int(42)); // allocate a new object
   // code that throws an exception that is not caught inside f
}
// auto_ptr freed automatically when function ends

auto_ptr 是可以容纳任意类型指针的模板:

auto_ptr<string> ap1(new string("Brontosaurus"));

std::auto_ptr的批评:

除了拥有”特殊”危险的能力–即能够被复制之外,它非常像一个域指针。允许被复制的能力也会意外地转移所有权。在最新的标准中已被弃用,你不应再使用它,而要使用 std::unique_ptr
(There is also std::auto_ptr. It is very much like a scoped pointer, except that it also has the “special” dangerous ability to be copied — which also unexpectedly transfers ownership! It is deprecated in the newest standards, so you shouldn’t use it. Use the std::unique_ptr instead.)

std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership. 
                                 // p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.

C++11提供了丰富的智能指针类型:std::unique_ptrstd::shared_ptr 以及std::weak_ptr


[1]. https://stackoverflow.com/questions/6121623/catching-exception-divide-by-zero
[2] C++ Primer 17.1 异常处理
[3] https://stackoverflow.com/questions/2331316/what-is-stack-unwinding
[4] https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值