异常机制

C++·语法 2019-01-10 5862 字 1074 浏览 点赞

组成

C++异常包括三部分:

  • 引发异常: throw
  • 处理异常: catch
  • 使用try块:try

异常提供了将控制权从程序的一个部分传递到另一部分的途径。

简单示例

#include <iostream>

using namespace std;

double sub(double z, double t);

int main(int argc, char *argv[])
{

    double z, t, y;
    while (cin >> z >> t)
    {
        // 可能引发异常的代码部分
        try {
            y = sub(z, t);
        }
        // 接收异常,由于下面的throw抛出
        // 的是字符串,所以char* 来接收
        catch (const char* s) {
            cout << s << endl;
            continue;
        }
        cout << "result: " << y << endl;
    }
    return 0;
}

double sub(double z, double t)
{
    if (t == 0) {
        // 抛出异常
        throw "argument error";
    }
    return z / t;
}

被引发的异常可以是字符串或其他C++类型,通常为类类型。发生异常未匹配到处理程序,默认调用abort()。

对象用作异常类型

引发异常的函数将传递一个对象,优点:

  • 使用不同的异常类型,区分不同的函数在不同的情况下引发的异常
  • 对象可以携带信息

示例:

double sub(double z, double t);

// 定义一个异常类
class BadArgument
{
private:
    double v1, v2;
public:
    BadArgument() {}
    void mesg();
};

inline void BadArgument::mesg()
{
    cout << "argument error.";
}

int main(int argc, char **argv)
{
    double z, t, y;
    while (cin >> z >> t) {
        try {
            z = sub(z, t);
        }
        catch (BadArgument& ba) {
            ba.mesg();
            continue;
        }
        cout << "result: " << z << endl;
    }
}

double sub(double z, double t)
{
    if (t == 0) {
        // 抛出异常
        throw BadArgument();
    }
    return z / t;
}

异常规范

C++ 98新增的一项功能,C++ 11摒弃了。(不建议遵守此规范)

double harm(double a) throw(bad_thing);  // may throw bad_thing exception
double marm(double) throw();  // does't throw an exception

throw()部分为异常规范,它可能出现在函数原型和函数定义中,可包含类型列表,也可不包含。

作用:

  • 告诉用户可能需要try
  • 让编译器添加执行运行阶段检查的码,检查是否违反了异常规范(很难实现)

关键字noexcept指明函数不会引发异常:

double marm() noexcept;

栈解退

函数流程:程序将调用函数的指令的地址放到栈中,当被调用的函数执行完后,程序使用这个地址来确定从哪里开始继续执行。

异常流程:程序释放栈中内存,但不会在释放第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址,随后将控制权转到块尾部的异常处理程序。

异常机制将负责栈中的变量。

其他异常特性

引发异常时,编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的时引用。之所以在失去引用传递快的特点下仍要使用引用,为的是:基类引用可以执行派生类对象

捕获全部异常:

try{
    ...
}
catch(...) {  // 省略号的意思是捕获全部异常
    ...
}

catch语句使用基类对象时,将捕获所有的派生类对象,但派生类的特性将被剥夺

class FException {
public:
    void msg() {
        cout << "This is f exception" << endl;
    }
};

class SException: public FException {
public:
    // 派生类重载方法
    void msg() {
        cout << "This is s exception" << endl;
    }
};

void throw_exception()
{
    throw SException();  // 抛出派生版本的异常
}

int main(int agrc, char ** argv)
{
    try {
        throw_exception();
    }
    // 用基类引用去捕获对象
    catch(FException& se) {
        se.msg();
    }
    return 0;
}

// 输出:
// This is f exception

但基类中使用虚函数可避免这种情况:

class FExecption {
    ...
    virtual void msg() {...}
}

// 其他不需要改动

// 输出:
// This is s exception

exception类

使用:#include <exception>

exception可以直接使用,也可以作为基类使用。其中存在一个what()的虚拟成员函数,返回一个字符串。该字符串的特征随实现而异。

class MyExec: public exception {
public:
    // what是基类的方法
    char* what() { cout << "this is my exec" << endl; }
};

void throw_execption()
{
    throw MyExec();
}

int main(int argc, char** argv)
{
    try {
        throw_execption();
    }
    catch(MyExec me) {
        me.what();
    }

    return 0;
}

stdexception异常类

头文件stdexception定义了其他几个异常类。logic_error和runtime_error类都继承自exception。这两个类用作两个派生类系列的基类。

login_error描述典型的逻辑错误:

  • domain_error:定义域异常,针对输入值不在预想的范围之中。
  • invalid_argument:当函数传递了一个意料之外的值。
  • length_error:当没有足够的空间来执行所需操作。
  • out_of_bounds:用于指出所引错误。其中,可以重载[]operator[]

runtime_error描述可能在运行期间发生但难以预计和方法的错误:

  • range_error:计算结果不在函数允许范围之内,但没发生上溢出或下溢出。
  • overflow_error:
  • underflow_error:存在浮点类型可以表示最小非零值,计算结果比这个值还小时将导致下溢错误。

一般而言,login表示可以通过编程修复,runtime表示存在但无法避免。

bad_alloc

使用new导致的内存分配问题,c++最新处理方式是让new引发bad_alloc异常(也来自exception),需要引用头文件new,旧版的返回一个空指针。

int main(int argc, char** argv)
{
    try {
        int* f = new int[1000000000000000000000];
    }
    catch (bad_alloc& ba) {
        cout << ba.what() << endl;
    }

    return 0;
}

// 输出:
// std::bad_array_new_length

异常、类和继承

就是在一个类中定义一个异常类。但是这样做的好处是什么,暂时不知道,留坑吧!

class Sales {
public:
    ...
    class bad_index: public std::log_error {
        ...
    }
}

异常分类

  • 意外异常(unexcepted exception):如果异常在带异常规范的函数中引发,则必须与异常列表中的异常匹配,否则称之为意外异常。
  • 未捕获异常(uncaught exception):如果异常不是在函数中引发的,或者是没有异常规范,这种异常必须捕获,否则称之为未补获异常。

未捕获异常不会导致程序立即终止,而是调用terminate(),默认情况下这个函数会去调用abort(),可以通过set_terminate()修改。

void ignore_exception()  // 未捕获异常处理方式
{
    cout << "don's care exception" << endl;
    exit(5);
}

void throw_exception()
{
    throw "please stop my program";
}

int main(int argc, char** argv)
{
    set_terminate(ignore_exception);  // 更改默认的abort()
    cout << "--one" << endl;
    throw_exception();
    cout << "--two" << endl;
    return 0;
}

// 输出:
// --one
// don's care exception

对于意外异常,如果需要全部捕获,可以利用set_unexcepted()转换:

void to_bad_exception()
{
    throw std::bad_exception();  // or just throw
}

int main()
{
    try {
        ...
    }
    catch (std::bad_exception& be) {
        ...
    }
}

注意事项

虽然栈解退会自动释放变量或是调用对象的析构函数,但以下这种情况会导出内存泄漏:

void test2()
{
    int* z = new int[10];
    if(on) {
        // 异常引发后会释放指针变量z,但不会释放z指向的内存
        throw exception();
    }
    delete[] z;
}

可以这样处理:

void test3()
{
    int* z = new int[10];
    try {
        if(on) {
            throw exception();
        }
    }
    catch(exception& exec) {
        delete[] z;  // 释放资源
        ...
    }
    delete[] z;
}


本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论