Skip to content

Ch 29: 错误处理

  • 理解 C++ 异常处理机制
  • 掌握标准异常类层次结构
  • 学会设计异常安全的函数
  • 理解 noexcept 的作用和影响
  • 掌握替代错误处理方式(optional、expected)
# Python 异常处理
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(f"Invalid value: {e}")
except (KeyError, TypeError) as e:
print(f"Key or type error: {e}")
except Exception as e:
print(f"Unknown error: {e}")
raise # 重新抛出
finally:
cleanup() # 始终执行

Python 异常特点:

  • 所有异常都是类,继承自 BaseException
  • 可以捕获几乎任何异常
  • 异常会传播到调用栈
  • finally 块始终执行
#include <stdexcept>
#include <iostream>
#include <string>
double divide(double a, double b) {
if (b == 0.0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
int main() {
try {
double result = divide(10.0, 0.0);
}
catch (const std::invalid_argument& e) {
std::cout << "Invalid argument: " << e.what() << "\n";
}
catch (const std::exception& e) {
std::cout << "Standard exception: " << e.what() << "\n";
}
catch (...) {
std::cout << "Unknown exception\n";
throw; // 重新抛出
}
// 没有 finally,RAII 处理资源清理
}

C++ 异常特点:

  • 异常层次结构清晰
  • 按引用捕获(避免切片)
  • catch(…) 捕获所有异常
  • RAII 替代 finally
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ ├── std::out_of_range
│ └── std::future_error (C++11)
├── std::runtime_error
│ ├── std::range_error
│ ├── std::overflow_error
│ ├── std::underflow_error
│ └── std::regex_error (C++11)
├── std::bad_alloc
├── std::bad_cast (C++11)
├── std::bad_typeid (C++11)
├── std::bad_exception (C++11)
└── std::nested_exception (C++11)
#include <stdexcept>
#include <iostream>
#include <vector>
#include <string>
// std::invalid_argument - 参数无效
void process_age(int age) {
if (age < 0 || age > 150) {
throw std::invalid_argument("Age must be between 0 and 150");
}
}
// std::out_of_range - 越界访问
int& at_index(std::vector<int>& v, std::size_t idx) {
if (idx >= v.size()) {
throw std::out_of_range("Index out of range: " + std::to_string(idx));
}
return v[idx];
}
// std::domain_error - 域错误(数学运算不合法)
double sqrt_safe(double x) {
if (x < 0) {
throw std::domain_error("Square root of negative number");
}
return std::sqrt(x);
}
// std::length_error - 长度超出允许范围
void reserve_too_much(std::vector<int>& v) {
if (v.size() > 1000000) {
throw std::length_error("Vector size exceeds maximum");
}
}
int main() {
try {
process_age(-5);
}
catch (const std::invalid_argument& e) {
std::cerr << "Invalid: " << e.what() << "\n";
}
try {
std::vector<int> v{1, 2, 3};
at_index(v, 10);
}
catch (const std::out_of_range& e) {
std::cerr << "Out of range: " << e.what() << "\n";
}
try {
sqrt_safe(-1.0);
}
catch (const std::domain_error& e) {
std::cerr << "Domain error: " << e.what() << "\n";
}
}
#include <iostream>
#include <new>
#include <typeinfo>
#include <string>
struct Base {
virtual ~Base() = default;
};
struct Derived : Base {
void specific_method() {}
};
int main() {
// std::bad_alloc - new 失败
try {
// 尝试分配巨大内存
void* p = operator new(sizeof(char) * 1000000000000000000ULL);
}
catch (const std::bad_alloc& e) {
std::cerr << "Allocation failed: " << e.what() << "\n";
}
// std::bad_cast - 动态转换失败
try {
Base* base = new Derived();
// 错误:base 实际指向 Derived,但转换类型不对
// Derived* d = static_cast<Derived*>(base); // 如果基类没有虚函数,这是 UB
delete base;
}
catch (const std::bad_cast& e) {
std::cerr << "Bad cast: " << e.what() << "\n";
}
}
#include <stdexcept>
#include <string>
#include <iostream>
// 继承 std::runtime_error 用于运行时错误
class NetworkError : public std::runtime_error {
public:
enum class ErrorCode {
ConnectionFailed,
Timeout,
HostNotFound,
ProtocolError
};
explicit NetworkError(const std::string& msg, ErrorCode code = ErrorCode::ConnectionFailed)
: std::runtime_error(msg), code_(code) {}
ErrorCode code() const { return code_; }
const char* what() const noexcept override {
return message_.c_str();
}
private:
ErrorCode code_;
mutable std::string message_;
// 缓存 what() 消息
void build_message() const {
message_ = "[NetworkError:" + std::to_string(static_cast<int>(code_)) + "] ";
message_ += std::runtime_error::what();
}
};
// 使用
void connect(const std::string& host) {
if (host.empty()) {
throw std::invalid_argument("Host cannot be empty");
}
if (host == "unreachable") {
throw NetworkError("Cannot reach host", NetworkError::ErrorCode::Timeout);
}
}
int main() {
try {
connect("unreachable");
}
catch (const NetworkError& e) {
std::cerr << "Network error: " << e.what() << "\n";
std::cerr << "Error code: " << static_cast<int>(e.code()) << "\n";
}
catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}
#include <stdexcept>
#include <iostream>
#include <string>
void low_level_operation() {
try {
throw std::runtime_error("Low level failure");
}
catch (...) {
// 捕获并重新抛出,携带当前异常作为原因
std::throw_with_nested(
std::runtime_error("Operation failed"));
}
}
void high_level_operation() {
try {
low_level_operation();
}
catch (...) {
std::throw_with_nested(
std::runtime_error("High level operation failed"));
}
}
int main() {
try {
high_level_operation();
}
catch (const std::nested_exception& e) {
std::cerr << "Top-level error: " << e.what() << "\n";
try {
e.rethrow_if_nested();
}
catch (const std::exception& inner) {
std::cerr << " Caused by: " << inner.what() << "\n";
}
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}
#include <exception>
#include <iostream>
// noexcept - 承诺不抛出异常
void safe_function() noexcept {
// 如果这里抛出异常,调用 std::terminate()
}
// noexcept 表达式
void test() {
constexpr bool can_throw = noexcept(safe_function());
// safe_function 声明为 noexcept,所以 can_throw 为 true
std::cout << "safe_function is noexcept: " << can_throw << "\n";
}
// 条件 noexcept
template<typename T>
void copy_vector(std::vector<T>& dest, const std::vector<T>& src) noexcept(
std::is_nothrow_copy_constructible_v<T>) {
// 只有当 T 的拷贝构造不抛异常时,这个函数才是 noexcept
}
#include <vector>
#include <iostream>
class Widget {
public:
Widget() { std::cout << "Construct Widget\n"; }
~Widget() { std::cout << "Destroy Widget\n"; }
// noexcept 移动构造函数
Widget(Widget&&) noexcept {
std::cout << "Move Widget\n";
}
Widget& operator=(Widget&&) noexcept {
std::cout << "Move assign Widget\n";
return *this;
}
// 非 noexcept 移动构造函数
Widget(const Widget&) {
std::cout << "Copy Widget\n";
}
};
int main() {
std::vector<Widget> vec;
// vector realloc 时:
// - 如果移动构造函数是 noexcept,使用移动
// - 否则使用拷贝(更安全)
std::cout << "=== push_back ===\n";
vec.push_back(Widget()); // 可能移动
std::cout << "\n=== reserve and push_back ===\n";
vec.reserve(10); // 预分配
vec.push_back(Widget()); // 移动
}
#include <vector>
#include <iostream>
#include <type_traits>
// vector 在 realloc 时的行为
// C++11/14: 如果移动操作 noexcept,使用移动
// C++17: 移动操作总是 noexcept(除非显式标记为可能抛出)
class MayThrow {
public:
MayThrow() = default;
MayThrow(MayThrow&&) {} // 可能抛出
};
class NoThrow {
public:
NoThrow() = default;
NoThrow(NoThrow&&) noexcept {} // 不抛出
};
int main() {
std::cout << "MayThrow move is noexcept: "
<< std::is_nothrow_move_constructible_v<MayThrow> << "\n";
std::cout << "NoThrow move is noexcept: "
<< std::is_nothrow_move_constructible_v<NoThrow> << "\n";
// 推荐:移动操作应该标记为 noexcept
// 这样 vector 可以优化 realloc
}
#include <vector>
#include <string>
#include <iostream>
// 1. 基本保证 - 异常后对象仍有效
class BasicGuarantee {
std::vector<int> data_;
public:
void push_back_safe(int value) {
auto copy = data_; // 拷贝副本
copy.push_back(value); // 可能抛出(内存分配失败)
data_ = std::move(copy); // 不会抛异常
}
// 即使 push_back 失败,data_ 仍保持原状态
};
// 2. 强保证 - 异常后状态不变
class StrongGuarantee {
std::vector<int> data_;
public:
void operation() noexcept {
auto copy = data_; // 拷贝
copy.push_back(42); // 可能抛出
data_ = std::move(copy); // noexcept
}
// 整个操作是原子性的:要么成功,要么状态不变
};
// 3. 不抛出保证 - 绝不抛出异常
class NoThrow {
int data_ = 0;
public:
void reset() noexcept {
data_ = 0; // 绝不抛异常的基本操作
}
};
#include <iostream>
#include <fstream>
#include <stdexcept>
class FileHandler {
std::fstream file_;
public:
FileHandler(const std::string& filename) {
file_.open(filename);
if (!file_.is_open()) {
throw std::runtime_error("Cannot open file");
}
}
~FileHandler() noexcept {
if (file_.is_open()) {
file_.close();
}
}
void write(const std::string& data) {
if (!(file_ << data << "\n")) {
throw std::runtime_error("Write failed");
}
}
};
void safe_operation() {
FileHandler file("test.txt");
file.write("data1");
file.write("data2");
// 即使 write 抛出异常,file 也会正确关闭
}
int main() {
try {
safe_operation();
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}
#include <optional>
#include <vector>
#include <iostream>
#include <string>
// 策略1:返回 std::optional
// 成功:返回包含值的 optional
// 失败:返回空 optional
std::optional<int> find_index(const std::vector<int>& v, int target) {
for (std::size_t i = 0; i < v.size(); ++i) {
if (v[i] == target) {
return static_cast<int>(i);
}
}
return std::nullopt; // 没找到
}
std::optional<std::string> get_config(const std::string& key) {
static std::vector<std::pair<std::string, std::string>> configs = {
{"host", "localhost"},
{"port", "8080"}
};
for (const auto& [k, v] : configs) {
if (k == key) {
return v;
}
}
return std::nullopt;
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 使用 optional
auto idx = find_index(nums, 3);
if (idx.has_value()) {
std::cout << "Found at index: " << idx.value() << "\n";
}
else {
std::cout << "Not found\n";
}
// 使用 value_or
auto port = get_config("port").value_or("80");
std::cout << "Port: " << port << "\n";
// 使用 and_then(C++23)
// auto result = get_config("host")
// .and_then([](const std::string& h) {
// return validate_host(h);
// });
}
#include <variant>
#include <iostream>
#include <string>
// 策略2:返回错误码
enum class Error {
NotFound,
InvalidInput,
PermissionDenied,
Unknown
};
std::variant<int, Error> divide(int a, int b) {
if (b == 0) {
return Error::InvalidInput;
}
return a / b;
}
int main() {
auto result = divide(10, 0);
if (std::holds_alternative<Error>(result)) {
switch (std::get<Error>(result)) {
case Error::InvalidInput:
std::cerr << "Invalid input\n";
break;
case Error::NotFound:
std::cerr << "Not found\n";
break;
default:
std::cerr << "Unknown error\n";
}
}
else {
std::cout << "Result: " << std::get<int>(result) << "\n";
}
}
#include <expected>
#include <iostream>
#include <string>
// C++23: std::expected - 类似 optional 但携带错误信息
std::expected<int, std::string> parse_int(const std::string& s) {
try {
return std::stoi(s);
}
catch (...) {
return std::unexpected("Failed to parse: " + s);
}
}
int main() {
auto result = parse_int("42");
if (result) {
std::cout << "Parsed: " << result.value() << "\n";
}
else {
std::cerr << "Error: " << result.error() << "\n";
}
// 使用 value_or
auto result2 = parse_int("abc");
std::cout << "Or default: " << result2.value_or(0) << "\n";
}
#include <stdexcept>
#include <iostream>
// 使用异常的情况:
// 1. 真正异常的情况(不应该发生的错误)
// 2. 错误不能被调用者合理处理
// 3. 需要传播到上层
class DatabaseConnection {
public:
void connect(const std::string& host) {
if (host.empty()) {
throw std::invalid_argument("Host cannot be empty");
}
// 连接失败应该抛异常
}
};
// 不使用异常的情况:
// 1. 常见可预期的错误(如查找失败)
// 2. 性能关键代码
// 3. 需要快速路径和慢速路径
std::optional<int> find_in_cache(int key); // 缓存查找,失败是正常的
int critical_path(int key); // 性能关键,不应抛异常
#include <vector>
#include <string>
#include <iostream>
// 模式1:Copy-and-swap(强异常安全)
class StringList {
std::vector<std::string> data_;
public:
void push_back_guaranteed(const std::string& s) {
auto copy = data_; // 拷贝
copy.push_back(s); // 可能抛异常
data_ = std::move(copy); // noexcept
}
};
// 模式2:资源获取即初始化(RAII)
class RAIIResource {
std::vector<int> data_;
public:
void modify() {
// 即使修改失败,原状态保持
auto backup = data_;
// ... 修改操作
}
};
// 模式3:noexecpt 操作替代异常
class SafeNumerics {
public:
static int add(int a, int b) noexcept {
if (a > 0 && b > INT_MAX - a) {
return INT_MAX; // 溢出处理,不抛异常
}
return a + b;
}
};
#include <stdexcept>
#include <vector>
// 文档化函数可能抛出的异常
class Widget {
public:
// 可能抛出 std::bad_alloc(从 push_back)
// 可能抛出 std::out_of_range(从 at)
void process() {
data_.push_back(42);
data_.at(0) = 100;
}
private:
std::vector<int> data_;
};
// noexcept 函数不应该抛出
class SafeClass {
public:
int get_size() const noexcept {
return data_.size();
}
bool is_empty() const noexcept {
return data_.empty();
}
private:
std::vector<int> data_;
};

异常 vs 其他错误处理:

方式适用场景优点缺点
异常真正异常传播自动可能有性能开销
optional可能无值明确调用繁琐
error code性能关键零开销可能被忽略
expected有错误信息完整C++23

异常安全级别:

级别保证说明
基本保证对象有效异常后不泄漏资源
强保证状态不变操作原子性
不抛异常无异常最低开销

noexcept 影响:

  • 移动构造函数 noexcept → vector realloc 使用移动
  • noexcept 函数抛出异常 → 调用 std::terminate()
  • 用于性能优化和 API 契约声明

最佳实践:

  • 用 RAII 管理资源
  • 移动操作标记 noexcept
  • 常用错误用 optional/error_code
  • 真正异常才用异常

下章预告:ch30 综合项目,使用 C++20 构建 CLI 任务管理器,整合所有学到的知识。