Exception API文档

概述

mc::exception 是一个统一的异常处理系统,为整个库提供了一致、可扩展的错误处理机制。它基于C++标准异常机制进行了扩展,提供更丰富的错误信息和更灵活的异常处理方式。

主要特性

  • 统一异常类层次结构:所有异常继承自 mc::exception 基类
  • 丰富的错误信息:包括错误代码、错误消息、文件名、行号和时间戳
  • 异常工厂:支持动态异常类型识别和重新抛出
  • 日志集成:异常可以包含多条日志消息,提供详细的错误上下文
  • 易用的宏:提供简单直观的宏简化异常的抛出和处理

基本用法

抛出异常

// 使用 MC_THROW 宏抛出预定义异常
MC_THROW(mc::timeout_exception, "连接超时: ${host}:${port}", ("host", "127.0.0.1")("port", "8080"));

// 使用条件断言
MC_ASSERT(ptr != nullptr, "指针不能为空");

// 捕获并重新抛出,添加更多上下文
try {
    // 可能抛出异常的代码
} catch (const std::exception& e) {
    MC_RETHROW_EXCEPTION(mc::system_exception, "文件操作失败: ${file}", ("file", file_path));
}

捕获和处理异常

try {
    // 可能抛出异常的代码
} catch (const mc::timeout_exception& e) {
    // 处理特定类型的异常
    std::cerr << "超时异常: " << e.what() << std::endl;
} catch (const mc::exception& e) {
    // 处理所有 MC 库异常
    std::cerr << "MC异常: " << e.code() << " " << e.name() << ": " << e.what() << std::endl;

    // 打印详细信息
    std::cerr << e.to_detail_string() << std::endl;
} catch (const std::exception& e) {
    // 处理标准库异常
    std::cerr << "标准异常: " << e.what() << std::endl;
} catch (...) {
    // 处理所有其他未知异常
    std::cerr << "未知异常" << std::endl;
}

自定义异常类

可以使用声明/实现宏定义自定义异常类(头文件声明,源文件实现):

// database_exception.h(头文件)
#include <mc/exception.h>

// 声明异常类(仅声明,不产生实现)
MC_DECLARE_EXCEPTION_CLASS(database_connection_exception, 200, "数据库连接失败", "db_connection");

// database_exception.cpp(源文件)
#include "database_exception.h"

// 生成实现(放在一个源文件内)
MC_IMPLEMENT_EXCEPTION_CLASS(database_connection_exception, 200, "数据库连接失败", "db_connection");

// 使用自定义异常
MC_THROW(database_connection_exception, "无法连接到数据库: ${server}", ("server", db_server));

API 详细说明

异常代码枚举

mc::exception_code 定义了可能的异常类型代码:

代码 枚举常量 描述
0 unknow_exception_code 未知异常代码
1 unhandled_exception_code 未处理的第三方异常
2 timeout_exception_code 超时异常
3 file_not_found_exception_code 文件未找到异常
... ... ...
100 variant_error_code 变体类型异常
101 dict_error_code 字典异常
... ... ...

exception 基类

mc::exception 是所有异常的基类:

方法 描述
exception(int64_t code, const std::string& name, const std::string& what) 构造函数
exception(mc::log::message&& msg, int64_t code, const std::string& name, const std::string& what) 构造函数(带日志消息)
int64_t code() const noexcept 获取异常代码
std::string_view name() const noexcept 获取异常名称
const char* what() const noexcept 获取异常描述(继承自 std::exception)
void append_log(mc::log::message msg) 添加日志消息
std::string to_detail_string(mc::log::level ll = mc::log::level::all) const 获取详细异常信息
std::string to_string(mc::log::level ll = mc::log::level::info) const 获取简要异常信息
const std::string& top_message() const 获取顶层异常消息
void dynamic_rethrow_exception() const 动态重新抛出异常
std::shared_ptr<exception> dynamic_copy_exception() const 动态复制异常

注意:以上 API 表格需要更新为完整的 6 列格式:| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |。

预定义异常类

库预定义了许多常用异常类:

异常类 异常代码 描述
timeout_exception timeout_exception_code 操作超时
file_not_found_exception file_not_found_exception_code 文件未找到
parse_error_exception parse_error_exception_code 解析错误
invalid_arg_exception invalid_arg_exception_code 无效参数
key_not_found_exception key_not_found_exception_code 键未找到
bad_cast_exception bad_cast_exception_code 类型转换错误
out_of_range_exception out_of_range_exception_code 索引越界
... ... ...

特殊异常类

error_exception 的响应恢复策略

mc::error_exception::from_response() 构造普通 error_exception,也适用于同进程跨 service 的错误响应。不增加异常子类,不包含 D-Bus 映射表,不依赖 mcengine。策略存于既有私有实现,不改变公开对象布局、原构造函数签名和虚表。

error_name_policy 包含 restore_from_json(普通构造的默认值,恢复 JSON 中的名字)和 preserve_exception_name(恢复诊断信息但保留异常名字)。正文转发由独立的 preserves_response_body() 控制;为 true 时使用原始 get_json_error() 或非 JSON 的 top_message(),不得重新编码。响应工厂同时设置这两项约束,不提供策略 setter;不能用名称策略代替正文策略判断。

方法 参数 返回值 异常 线程安全 说明
static from_response(mc::string_view error_name, mc::string_view error_json) 响应名、已确认可由 error::decode 恢复的 JSON error_exception 内存分配异常 输入未被并发修改时安全 保留名字及原始 JSON,不解析 JSON,不映射名字
static from_response(mc::string_view error_name, mc::log::message&& message) 响应名、诊断消息 error_exception 内存分配异常 输入未被并发修改时安全 用于普通文本、空正文及展示用 JSON
name_policy() const noexcept 无 error_name_policy 无 无并发修改时安全 名称恢复策略
preserves_response_body() const noexcept 无 bool 无 无并发修改时安全 是否原样转发正文,普通构造为 false
dynamic_copy_exception() 无 std::shared_ptr<exception> 内存分配异常 无并发修改时安全 复用原有实现,复制策略及 JSON
dynamic_rethrow_exception() 无 不返回 抛出 error_exception 无并发修改时安全 复用原有实现,保留策略
#include <mc/error.h>
#include <mc/exception.h>
#include <cassert>

int main()
{
    auto diagnostic = mc::make_error("internal.missing", "missing object")->encode();
    auto remote = mc::error_exception::from_response("public.MissingObject", diagnostic);
    auto restored = mc::error::from_exception(remote);
    assert(restored->get_name() == "public.MissingObject");
    assert(remote.get_json_error() == diagnostic);
}

✅ 通过响应工厂构造接收到的错误,策略随 error_exception 值复制、future 和重新抛出保留。✅ 内部错误的序列化与 raise() 继续使用原有构造函数。

❌ 不按值复制成通用 exception,否则会切掉 error_exception 的 JSON 数据。❌ 不将响应错误名一律反向替换为内部名称,通用失败无法恢复其原始原因。

异常类 描述
unhandled_exception 包装未处理的第三方异常
std_exception_wrapper 包装标准库异常

统一标准异常集合

库通过宏 MC_STD_EXCEPTION_CLASS 统一声明了常见标准异常类型(如 timeout_exception、parse_error_exception 等),无需手动定义即可使用。

辅助宏

宏 描述
MC_THROW(exception_class, message, ...) 抛出指定类型异常(内部使用 MC_MAKE_EXCEPTION 构造消息)
MC_ASSERT(condition, message, ...) 条件断言失败时抛出 assert_exception
MC_ASSERT_THROW(condition, exception_class, message, ...) 条件断言失败时抛出指定异常
MC_RETHROW_EXCEPTION(exception_obj, message, ...) 在已有异常对象上追加日志并重新抛出
MC_CAPTURE_AND_WRAP_EXCEPTION(message, ...) 捕获 std::exception 并包装为 std_exception_wrapper
MC_DECLARE_EXCEPTION_CLASS/MC_IMPLEMENT_EXCEPTION_CLASS 声明/实现自定义异常类

最佳实践

  1. 使用预定义异常:优先使用库提供的预定义异常类,而不是自定义异常

  2. 提供详细错误信息:抛出异常时提供充分的上下文信息,包括具体的错误原因和相关参数

  3. 异常粒度:根据需要使用适当粒度的异常处理,既不要过于笼统,也不要过于细化

  4. 异常传播:适当使用重新抛出机制,在保留原始异常信息的同时添加上下文信息

  5. 资源安全:确保在异常处理过程中正确释放资源,推荐使用 RAII 模式

✅ 正例

// 1. 抛出异常时携带充分的上下文信息
void connect(const std::string& host, int port) {
    if (port <= 0 || port > 65535) {
        MC_THROW(mc::invalid_arg_exception,
                 "无效端口号: ${port}, 有效范围 1-65535",
                 ("port", port));
    }
    // 连接逻辑...
}

// 2. 按具体到通用的顺序捕获异常
try {
    process_data();
} catch (const mc::timeout_exception& e) {
    // 先捕获最具体的异常类型
    std::cerr << "超时: " << e.what() << std::endl;
    retry();
} catch (const mc::exception& e) {
    // 再捕获通用 mc 异常
    std::cerr << "MC异常[" << e.code() << "]: " << e.what() << std::endl;
} catch (const std::exception& e) {
    // 最后捕获标准库异常
    std::cerr << "标准异常: " << e.what() << std::endl;
}

❌ 反例

// 1. 抛出异常时不携带上下文,难以定位问题
void connect(const std::string& host, int port) {
    if (port <= 0) {
        MC_THROW(mc::invalid_arg_exception, "端口无效");  // ❌ 没有说明哪个端口、合法范围
    }
}

// 2. 用 catch(...) 吞掉所有异常,丢失错误信息
try {
    process_data();
} catch (...) {
    // ❌ 没有任何日志或处理,异常信息完全丢失
    // 至少应记录日志或重新抛出
}