23 July 2023

C++ 核心指南目录

“Throw an exception to signal that a function can’t perform its assigned task”

理由

这种方法可以把出错处理过程变得更系统、稳固,且避免代码重复。

例子

struct Foo {
    vector<Thing> v;
    File_handle f;
    string s;
};

void use()
{
    Foo bar {{Thing{1}, Thing{2}, Thing{monkey}},
             {"my_file", "r"}, "Here we go!"};
    // ...
}

这里, vectorstring 的构造函数可能无法分配到足够的内存给其内部元素。 vector 构造函数可能无法把初始化列表中的东西复制过来, File_handle 可能不能打开文件。在各种可能出错的情况下,都会抛出异常给调用函数 use() 来处理。 use() 函数可以通过 try/catch 来处理构造 bar 的过程中出现的错误。出现错误的时候, Foo 的构造函数会在转交控制权之前,正确销毁已经构造的成员。

注意,这里没有包含错误码的返回值。

File_handle 构造函数可能可以这样定义:

File_handle::File_handle(const string& name, const string& mode)
    : f{fopen(name.c_str(), mode.c_str())}
{
    if (!f)
        throw runtime_error{"File_handle: could not open "
                            + name + " as " + mode};
}

注意

人们经常说异常意味着异常的事件或者异常的错误的信号。但是,这里有些同义反复,到底什么是异常的?

例子

  • 一个前提条件没法满足
  • 一个构造函数无法构造一个对象(构造一个类的不变式的过程失败了)
  • 超出范围错误(比如, v[v.size()] = 7
  • 无法获取一个资源(比如,网络连接断开)

相反的,一个普通循环的结束,不算是异常的。除非这个循环是无限循环。这时候,提前结束意味着异常情况。

注意

不要通过 throw 作为从函数返回值的一种简单替代。

例外情况

像硬实时系统之类的系统,会要求固定的最大时间内响应事件。这时候,我们必须有工具确保在指定时间内能从 throw 恢复的情况。

注意

如果你无法承受异常,或者不喜欢基于异常的出错处理,请在做出决定之前,考虑其他选项。当然这些选项有其自身的复杂性和问题。一定要记住,在申明性能效率之前,要做出一定的度量。