运行时错误 Run-Time Error
异常对象
使用异常对象记录异常信息
// 一个异常对象的示例
class VectorIndexError {
public:
VectorIndexError(int v) : m_badValue(v) {}
void diagnostic() {
cerr << "Index " << m_badValue << " out of range!" << endl;
}
private:
int m_badValue;
};
异常对象常在 throw
语句中使用构造函数进行构造
异常对象之间常常具有继承关系
// 基类:数学错误
class MathErr {
public:
virtual void diagnostic() = 0; // 纯虚函数,要求派生类实现
};
// 派生类1:溢出错误
class OverflowErr : public MathErr {
public:
void diagnostic() override { /* 实现细节 */ }
};
// 派生类2:下溢错误
class UnderflowErr : public MathErr {
public:
void diagnostic() override { /* 实现细节 */ }
};
// 派生类3:除零错误
class ZeroDivideErr : public MathErr {
public:
void diagnostic() override { /* 实现细节 */ }
};
标准库定义多种异常对象(Standard Library Exceptions),其继承关系如图所示;自定义的异常类型通常继承于标准库异常对象
throw
语句
格式一:throw e
抛出值 e,其中 e 通常为异常对象,但也可为 int、string 等普通的对象或值
template <class T>
T& Vector<T>::operator[](int idx) {
if (idx < 0 || idx >= m_size) {
throw VectorIndexError(idx); // 使用构造函数构造一个异常对象,并将其抛出
}
return m_elements[idx];
}
格式二:throw
在 catch 块中使用,用于将该 catch 块捕获的异常对象重新抛出,从而将异常信息沿函数调用链(Call Chain)逐层向外传导
noexpect
标签
由于异常处理需要代价,可在函数声明中加入 noexpect
标签,表明该函数内部无需进行异常处理,从而帮助编译器提高代码运行效率
void func(int a) noexcept {...}
若带 noexpect
标签的函数内部试图抛出异常,则触发 std::terminate
;
std::terminate
一般情况下会导致程序的中断
可以自定义 std::terminate
的行为
void my_terminate() {...}
set_terminate(my_terminate);
new
语句发出的异常
new
语句发生异常时,不会返回 0 或空指针,而是发出 bad_alloc()
异常Try-Catch 块
try {
// code to exercise math options
throw UnderFlowErr(); // 触发下溢错误
} catch (ZeroDivideErr& e) {
// 处理除零错误
} catch (UnderFlowErr& e) {
// 处理数值下溢错误
} catch (MathErr& e) {
// 处理其他数学相关错误(基类捕获)
} catch (...) {
// 捕获所有未被上述catch处理的异常(兜底处理)
}
catch (sometype v)
捕获某个具体类型的参数catch (...)
捕获所有未被前述 catch 语句捕获的异常捕获顺序
UnderFlowErr
将被第一个 catch 语句捕获,而不会被第二个 catch 语句捕获// 反例
try {
// code to exercise math options
throw UnderFlowErr(); // 触发下溢错误
} catch (MathErr& e) {
// 处理除零错误
} catch (UnderFlowErr& e) {
// 处理数值下溢错误
} catch (...) {
// 捕获所有未被上述catch处理的异常(兜底处理)
}
未被捕获的异常
std::terminate
异常处理器的参数类型
// 异常处理器一般接收引用类型参数
catch (Exception& e) {...}
// 如果接收指针类型参数,则指针指向的动态空间何时被销毁不易确定,可能导致内存泄漏
catch (Exception* p) {...}
// 如果是值传递,则向上造型时会发生截断,导致类型信息和部分字段的丢失
catch (Exception p) {...}
new
分配的空间,打开的文件等)进行释放init()
函数中实现