CppCoreGuidelines E.2 用抛出异常作为信号说明函数无法处理分配给它的任务
23 July 2023
“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!"}; // ... }
这里, vector
和 string
的构造函数可能无法分配到足够的内存给其内部元素。
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
恢复的情况。
注意
如果你无法承受异常,或者不喜欢基于异常的出错处理,请在做出决定之前,考虑其他选项。当然这些选项有其自身的复杂性和问题。一定要记住,在申明性能效率之前,要做出一定的度量。