面试题答案
一键面试异常规范被弃用的原因
- 运行时开销:异常规范在运行时需要额外的检查,这增加了程序的性能开销。例如,函数调用时需要检查函数是否可能抛出不匹配异常规范的异常,这会引入额外的指令。
- 维护困难:随着代码的演进,函数的实现可能改变,导致异常规范难以维护。如果函数实现修改后抛出了不符合异常规范的异常,程序行为将变得未定义。
- 缺乏灵活性:异常规范是一种严格的声明,限制了函数可能抛出的异常类型。这在实际开发中可能过于死板,无法适应复杂多变的需求。
现代C++中替代的异常安全策略
- 异常安全保证:
- 基本保证:如果异常抛出,程序的状态不被破坏,所有对象处于有效状态。例如,在容器操作中,如果插入元素时抛出异常,容器本身不会处于不一致状态。
- 强烈保证:如果异常抛出,程序状态回滚到操作前的状态,即“事务性”语义。例如,在进行多个相关操作时,若其中一个操作失败抛出异常,所有已完成的操作将被撤销。
- 不抛出保证:函数承诺绝不抛出异常,通常用于移动构造函数、析构函数等对异常敏感的函数。
- RAII(Resource Acquisition Is Initialization):通过对象的构造和析构来管理资源。例如,使用
std::unique_ptr
管理动态分配的内存,std::lock_guard
管理互斥锁等。当对象超出作用域时,析构函数会自动释放资源,即使在析构函数之前抛出异常也能保证资源的正确释放。
跨平台项目中编写可移植的异常处理代码思路
- 使用标准库:依赖C++标准库提供的异常机制,如
std::exception
及其派生类。避免使用平台特定的异常类型或处理方式。 - 封装系统调用:将底层系统调用封装在函数或类中,在封装层处理可能的异常并转换为标准异常。这样上层代码无需关心不同平台系统调用的异常差异。
- 条件编译:在某些情况下,可能需要针对不同平台编写特定的代码,但应尽量减少这种情况。例如,在Windows上,系统调用可能通过
GetLastError
获取错误码,而在Linux上可能通过errno
。可以使用条件编译来处理这种差异。
示例代码框架
#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <cerrno>
#ifdef _WIN32
#include <windows.h>
#endif
// 封装系统调用
void platform_specific_call() {
#ifdef _WIN32
// Windows 系统调用示例
if (!SomeWindowsAPI()) {
DWORD error = GetLastError();
throw std::runtime_error("Windows API call failed with error: " + std::to_string(error));
}
#else
// Linux 系统调用示例
if (someLinuxSystemCall() == -1) {
throw std::runtime_error("Linux system call failed with error: " + std::string(strerror(errno)));
}
#endif
}
// 上层调用函数
void higher_level_function() {
try {
platform_specific_call();
} catch (const std::runtime_error& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
// 可以在这里进行进一步处理,如记录日志、恢复状态等
}
}
int main() {
higher_level_function();
return 0;
}
在上述示例中,platform_specific_call
函数封装了平台特定的系统调用,并将其可能产生的错误转换为标准的std::runtime_error
异常。higher_level_function
函数捕获这些异常并进行统一处理,使得上层代码无需关心底层平台的差异。这样的代码框架有助于在跨平台项目中编写可移植的异常处理代码。