C++之旅(学习笔记)第4章 错误处理

C++之旅(学习笔记)第4章 错误处理

4.1 异常

当我们试图越界访问Vector动态数组时,应该发生什么?

假定可从下标越界的访问错误中恢复,那么Vector类的解决方案是实现者检测所有的越界访问并且告知用户。然后用户执行合适的操作。

例如:Vector::operator可以检测所有越界访问并且抛出out_of_range异常:

double& Vector::operator[](int i) {
    if(!(0 < i && i < size()))
        throw out_of_range{"Vector::operator[]"};
    return elem[i];
}
  • throw指令创建了一个out_of_range类型的异常,并将异常的控制权转移给直接或者间接调用Vector::operator函数的用户。
  • 要做到这点,编译器的实现需要回溯函数的调用栈并且找到调用者的上下文。
  • 这意味着异常处理机制将退出当前作用域并且把上下文回溯到对该异常感兴趣的调用者,在这个过程中可能会调用析构函数。
void f(Vector& v)
{
    //...
    try{							//在这个区块抛出的out_of_range异常,使用下方处理器处理
        compute1(v);				//可能会试图访问v的结束以外的范围
        Vector v2 = compute2(v);	//可能会试图访问v的结束以外的范围
        compute3(v2);				//可能会试图访问v2的结束以外的范围
    }
    catch(const out_of_range& err){//哎呀,发生了out_of_range错误
        //处理这个错误
        cerr << err.what() << '\n';
    }
}
  • 将想要捕获异常的代码放进一个try代码块。此处的catch语句用来处理类型为out_of_range的异常。
  • out_of_range类型在标准库()中定义,事实上也已经被一些容器访问类标准库函数使用。
  • 这里使用引用来捕获异常以避免对异常变量的复制,同时使用what()函数打印输出被throw抛出的错误信息。

异常处理机制可以是错误处理更简单、更系统化,同时提升可读性。

让异常处理变得简单与系统化的主流技术(名为资源获取即初始化,RAII)。

RAII的基本思想:让构造函数负责获取类需要的资源,同时让析构函数负责释放资源,这样就可让资源释放可靠地自动进行。

4.2 断言机制

4.2.1 assert

标准库提供了一个调试宏,assert(),它可以在运行时断言必须满足的条件。

例如:

void f(const char* p) {
    assert(p != nullptr);//p不可以是nullptr
    // ...
}

如果assert()的条件不满足,在调试模式下,程序终止,在非调试模式下,assert()不被检查。这个功能简单粗暴而且不够灵活,当然也比什么都不做要强。

4.2.2 static_assert

异常用于报告在运行时发现的错误。但只要有可能,我们倾向于尽量让错误可以在编译时被发现。

static_assert(4 <= sizeof(int), "integers are too small");//检查整数大小

如果4 <= sizeof(int)不满足(意思是:如果系统中的int类型不具备至少4字节长度)。则会输出integers are too small。我们把这种语句叫做断言。

静态断言机制可以使用任何表达式:

constexpr double C = 299792.458;					// km/s
void f(double speed) {
    constexpr double local_max = 160.0/(60*60);		// 160km/h = 160.0/(60*60)km/s
    static_assert(speed < C, "can't go that fast");	// 错误:speed必须是常量
    static_assert(local_max < C, "can't go that fast");// 可行
    //...
}
  • 一般来说,如果A不为真,那么static_assert(A,S)打印输出S作为编译错误信息。
  • 如果你不需要某个特定的信息,可以忽略S参数,编译器会生成默认信息:static_assert(4 <= sizeof(int));//使用默认信息
  • 典型的默认信息通常由static_assert调用代码的位置加上断言内容的字符表述谓词构成。
  • 静态断言的一大重要用途是在泛型编程中对类型参数进行断言。

4.2.3 noexcept

如果一个函数绝不应当抛出异常,那么可以将它声明为noexcept。

void user(int sz) noexcept
{
    Vector v(sz);
    iota(&v[0],&v[sz],1);//将v填充为1,2,3,4...
    //...
}

如果所有的意图与设计都失败,user()函数依然抛出异常,系统会立即调用std::terminate中止这个程序。

4.3 建议

  1. 打开文件失败或到达迭代结束是预期事件而不是异常;
  2. 在错误需要通过多层函数调用向上渗透时,抛出异常;
  3. 如果不确定是使用异常还是错误代码,首选异常;
  4. 优先使用RAII,而不是直接用try代码块;
  5. 能在编译时检查的问题尽量在编译时检查;
  6. 使用断言机制对故障进行单点控制;
  7. Concepts是编译时的断言,因此经常用在断言中;
  8. 如果你的函数不允许抛出异常,那么把它声明为noexcept;
  9. 除非经过全面考虑,否则不要使用noexcept;

你可能感兴趣的:(C++,c++,学习,笔记)