内容要点:

  1. 程序终止的方式有好坏
    • 正确结构化的终止:比如通过函数返回值或异常机制,让调用者能够知道发生了什么并处理。
    • exit 之类的函数:直接终止程序,绕过调用栈,不让调用者做处理。
  2. exit 的问题
    • 隐藏控制流(Hidden control flow)
      • goto 使得函数内部的控制流难以理解。
      • exit 使得函数间的控制流难以理解:函数内部直接终止程序,调用者无法知道发生了什么。
    • 全局状态(Global state)
      • 通常认为全局状态指的是全局变量,但更广义上,还包括程序的非局部影响(比如文件、数据库、网络状态)。
      • 直接 exit 会导致非局部状态改变被“悄悄”触发,难以追踪。
        总结exit 虽然简单,但会破坏程序结构、可维护性和可预测性,不建议在库或可重用代码中随意使用。
        内容要点:
  • 不要把多个不相关的决策耦合在一起
    • 遵循单一职责原则(Single-Responsibility Principle)。
    • 耦合决策会降低代码复用能力。
  • 不要在代码中“污染”非局部关注点(non-local concerns)
    • 例如:一个函数的错误导致程序直接退出,这把终止责任强行加给了函数本身,而终止本应该由更高层的上下文决定。
  • 终止程序不是局部责任
    • 决策应该延迟到程序有足够上下文可以作出适当决定时再执行(例如在顶层的 main 函数或者错误处理框架)。
      总结:把程序终止责任放到局部函数,会破坏模块化设计。应该将错误处理和终止决策交给更高层次的上下文。
      内容要点:
  • ENOENT(文件或目录不存在)为例,错误信息本身是标准的,但如果缺少上下文(比如哪个路径不存在),就不够有用。
  • 上下文丢失问题
    • 调用 open 时,路径信息是可用的,但某些层级在传递过程中丢掉了这个信息。
    • 原因可能有:
      • 结构性原因:程序没有设计好保存错误上下文的机制。
      • 功能性原因:程序可用的机制没有正确传递或保存上下文信息。
        总结:错误信息要有上下文,否则用户无法理解或定位问题。
        内容要点:
  • int atoi(const char* str); 设计只针对成功情况:
    • 输入合法数字字符串 → 返回整数
  • 问题:如果字符串不是整数
    • atoi 返回 0,但字符串也可能表示数字 0
    • 不能区分“解析失败”与“解析结果为 0”
    • 假设输入总是合法数字,这是不安全的设计
  • 隐患:忽略尾部非数字字符,也没有错误报告机制。
    总结:设计仅考虑成功情况的 API 会导致错误被默默忽略,缺乏健壮性。

整数解析、错误报告机制、以及现代 C++ 的 std::error_code/std::error_condition

optional<int> real_atoi(const char* str) noexcept {
    const auto result = std::atoi(str);
    if (result) {
        return result;
    }
    while (*str && std::isspace(static_cast<unsigned char>(*str))) {
        ++str;
    }
    if (*str == '-') { ++str; }
    if (*str == '0') { return 0; }
    return nullopt;
}

理解:

  • 这是对 atoi 的改进封装,返回 optional<int>,用来明确表示 解析失败
  • 逻辑:
    1. 调用 std::atoi
    2. 如果结果不为 0,返回结果。
    3. 如果结果为 0,手动检查字符串:
      • 跳过空格
      • 跳过负号
      • 如果剩下的是 '0' → 返回 0
      • 否则返回 nullopt → 表示解析失败
  • 目的:解决 atoi 的问题,即不能区分 “输入非整数” 和 “输入是数字 0”。

11 页 — strtol

long strtol(const char* str, char** str_end, int base);

理解:

  • strtol 更好地支持错误检测:
    • 成功:*str_end 指向解析到的最后一个字符之后
    • 失败:*str_end 指向输入字符串的开头
  • 返回值仍可能为 0(所以仅凭返回值无法判断成功/失败)
  • 可以区分成功与失败,但无法轻易区分 不同类型的错误错误位置

12 页 — from_chars

struct from_chars_result {
    const char* ptr;
    errc ec;
};
from_chars_result from_chars(const char* first, const char* last, T& value, int base = 10);

理解:

  • from_chars 是 C++17 提供的整数/浮点解析机制,显式返回错误信息
    • ec(error code)可区分:
      • 溢出
      • 非整数字符串
    • ptr 指向解析停止的位置
  • 局限ptr 在失败时指向 first,无法精确指出失败发生的具体位置。

13 页 — Fail Fast, Fail Often

  • 上述解析函数通常忽略 前导空格
    • 调用者可能希望前导空格视为错误
  • 如果解析函数忽略空格,就等于 替用户做了决策 → 减少灵活性
  • 建议:调用者自己跳过空格,或者严格处理空格。

14 页 — Error Vocabulary

  • 传统 C 风格错误报告
    • int 或枚举表示错误(如 errnoCURLcode
    • 在单一调用下可用,但 组合调用时容易丢失上下文
  • 例如:一个函数依次调用 POSIX 与 libcurl 函数 → 如何传递和组合错误信息?

15 页 — std::error_code

  • 整数错误码错误类别(category) 结合
  • category 决定错误码如何解释
  • 同样的数值,不同 category 表示不同错误
  • category 是 std::error_category 的单例
  • 指针身份唯一性:通过指针判断 category 是否相等

16 页 — Error Handling 示例

  • 文件不存在:
    • errnoENOENT
    • CURLcodeCURLE_FILE_COULDNT_READ_FILE
  • C 风格只能对少数已知值做判断
  • std::error_code 可理论上处理无限多的错误类型和组合

17-18 页 — std::error_condition 与自定义错误类别

  • std::error_condition:用于表示根本原因,可编程处理
  • 可与 std::error_code 比较
  • 自定义 std::error_category
    • 提供 name()message()default_error_condition()
    • equivalent 用于判断错误码是否相当
  • 示例:自定义 decimal parser 错误枚举:
enum class error { success = 0, bad_whole, no_decimal, bad_decimal };
std::error_code make_error_code(error e) noexcept {
    static const struct : std::error_category { ... } category;
    return std::error_code(static_cast<int>(e), category);
}
namespace std {
    template<>
    struct is_error_code_enum<error> : true_type {};
}

总结

  • std::error_code + std::error_category 提供可组合、可扩展、类型安全的错误报告机制
  • 自定义 error 枚举可以直接与标准错误体系兼容

std::system_error、异常处理设计、FIX 消息解析、以及多线程错误处理

1. std::system_error

  • 定义
    • std::system_error 是一个异常类型,封装了一个 std::error_code
  • 用途
    • 可以继承并重载 what(),附加上下文信息。
    • 调用栈上可以捕获 std::system_error 并处理,也可以捕获 std::exception 并打印 what()
  • 前提:使用此类型意味着你的设计是允许抛出异常的。

2. 异常的使用原则

  • 常见观点:异常用于“特殊情况(exceptional situations)”
  • 问题:认为某些事情“特殊”,等于替用户做决策
  • 优点
    • 错误报告简单:直接 throw
    • 上下文传播方便:可在异常类型中添加额外信息,自定义 what()
  • 缺点
    • 错误处理复杂:不知道应该 catch 哪个异常
    • 代码分析困难:不清楚哪些操作会失败以及如何失败
  • 结论:构建块越高层,使用异常越合理

3. FIX 消息解析

  • 问题示例
8=FIX.4.2\x019=00238\x0135=D\x0134=160\x0149=P98004N\x015a=004\x0152=2
                                                        ^
Tag could not be parsed as an integer
  • 解析器需要提供错误上下文(指针、位置、错误类型)
  • 设计方式
    • fix_message_reader 返回 fix_message* 并通过 std::error_code& ec 输出错误信息
    • 提供额外方法:
      • format_last_error() → 返回格式化错误字符串
      • last() / last_begin() / last_end() / begin() / end() → 提供解析位置指针
  • standard_fix_client
    • last_error_source() → 枚举错误来源 (parsable, verify, parse_fix, parse_unknown, stop, other)
    • 提供 message_reader() 接口访问消息解析器
      总结
  • 解析器设计中,错误通过返回值 + std::error_code + 上下文方法的组合报告
  • 避免直接抛出异常,也不丢失位置信息和错误来源

4. 多线程错误处理(第 28-30 页)

  • 问题
    • 多线程环境中,“通过返回值报告错误”不再合理
    • 必须能够:
      1. 收集所有线程的错误
      2. 在任意线程发生错误时停止线程池
  • 实现方式thread_pool 类示例
class thread_pool {
    struct state : ::asio::io_context { std::thread thread; };
    std::list<state> states_;
    mutable std::mutex m_;
    std::exception_ptr ex_; // 捕获异常
public:
    explicit thread_pool(unsigned threads);
    void run();
    void stop(std::exception_ptr ex = std::exception_ptr()) noexcept;
};
  • 运行线程池
void thread_pool::run() {
    const auto run = [&](auto&& ctx) noexcept {
        try {
            ctx.run();
        } catch (...) {
            stop(std::current_exception());
        }
    };
    auto begin = std::next(states_.begin(), 1);
    const auto g = make_scope_exit([&]() noexcept {
        for (auto iter = std::next(states_.begin(), 1); iter != begin; ++iter) {
            iter->stop();
            iter->thread.join();
        }
    });
    for (const auto end = states_.end(); begin != end; ++begin) {
        begin->thread = std::thread([&, begin]() noexcept { run(*begin); });
    }
    run(states_.front());
    const std::lock_guard g(m_);
    if (ex_) std::rethrow_exception(std::move(ex_));
}

理解:

  • 每个线程在执行时捕获异常并通过 stop(std::current_exception()) 传递
  • 使用 std::exception_ptr 存储异常,保证线程安全收集
  • 最后在主线程中重新抛出异常,统一处理
  • 这种模式可以:
    1. 收集多线程异常
    2. 停止所有线程
    3. 保持异常信息

整体总结

  1. std::system_error:用于异常化错误报告,封装 std::error_code
  2. 异常设计原则:异常可简化错误传播,但会复杂化处理和分析
  3. FIX 消息解析:错误通过返回值 + std::error_code + 上下文方法报告
  4. 多线程错误处理:使用 std::exception_ptr 收集线程异常,保证统一停止与处理

错误的主观性、成功/失败的意义、事件回调设计、以及警告/日志策略

1. 错误是谁的?(Whose Error?,第 33 页)

  • 错误的判定依赖于上下文
    1. 抽象层次(Level of abstraction)
      • read 操作通常不把到达文件末尾(EOF)当作错误。
      • 但是,如果目标是填充缓冲区,EOF 可能被当作错误。
      • 在连接管理中,流结束可能不被视为错误。
    2. 目的(Purpose)
      • 无效 XML → 解析 XML 时是错误
      • 同时,用来猜测文件是否可能是 XML → 不是错误
        总结:错误不是绝对的,而是与上下文和意图相关。

2. 成功、失败,谁在乎?(Succeed, Fail, Who Cares?,第 34 页)

  • 例子:TCP 连接
    • 对客户端来说,成功可能意味着“收到 goodbye 消息”或“优雅关闭”
    • 对服务器来说,连接丢失本身就是结果,不管成功/失败细节
    • 成功与失败在服务器的处理方式可能是一样的 → 结果 vs. 过程的区别
  • 设计原则:不要让错误或成功判定影响整体流程,尤其在高层系统中。

3. Processor Manager 回调设计(第 34-38 页)

  • 核心类
struct processor_manager {
    explicit processor_manager(const processor_manager_settings& settings);
    void add_device(device& d);
    void add_feed(feed& f);
    void start();
    void stop() noexcept;
    void subscribe(processor_manager_callback& callback);
};
  • 回调事件
struct processor_manager_callback {
    virtual void on(const device_processor_begin& e) = 0;
    virtual void on(const packet_processor_begin& e) = 0;
    virtual void on(const device_processor_end& e) = 0;
    virtual void on(const packet_processor_end& e) = 0;
};
  • 事件参数
    • device_processor_end:包含 std::error_code ecstd::exception_ptr ex、以及处理对象 device* which
    • packet_processor_end:包含 std::exception_ptr ex、以及处理对象 session* which
  • 扩展示例eof_processor_manager_callback
    • 提供 processor source()name()wait()eof()maybe_throw() 等方法
    • 用于报告 EOF 或其他终止状态
      总结
  • 回调机制将 处理开始/结束、成功/失败 抽象出来
  • 允许调用者决定如何处理错误,而不是硬编码行为

4. 警告与日志(Warnings & Logging,第 40 页)

  • 特性
    • 带外通信(Out of band communication)
    • 可以在成功时发出警告,失败时记录日志
  • 注意
    • 日志不应作为长期替代错误报告的手段
    • 日志不应耦合到组件内部 → 应通过事件发布
    • 消费事件的独立组件负责写日志
      总结
  • 警告和日志应作为独立的渠道,用事件驱动方式处理
  • 避免组件内部耦合,保持模块化

5. 总结(第 41 页)

  • 不要:
    1. 假设失败不会发生
    2. 代替用户做不必要的决策
    3. 丢弃潜在有用的上下文
      核心原则
  • 错误处理应透明、可组合、可传递上下文
  • 成功/失败应根据上下文定义,不要在系统内部随意判定
  • 警告和日志应独立处理,而不是混入业务逻辑

完整的 C++ 示例,把你前面几页讲的错误处理原则、整数解析、std::error_code、异常、回调、日志、多线程都结合起来,形成一个可编译的示例。这个示例是一个简化版的“设备消息处理器”,包括:

  • 自定义整数解析 from_chars + 错误码
  • processor_manager 回调机制
  • 多线程处理
  • 错误/异常收集
  • 警告/日志事件机制
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <list>
#include <optional>
#include <system_error>
#include <exception>
#include <charconv>
#include <functional>
// ===========================
// 1. 自定义整数解析
// ===========================
enum class parse_error { success = 0, not_integer, overflow };
struct parse_category : std::error_category {
    const char* name() const noexcept override { return "parse_category"; }
    std::string message(int code) const override {
        switch (static_cast<parse_error>(code)) {
            case parse_error::success: return "Success";
            case parse_error::not_integer: return "Not an integer";
            case parse_error::overflow: return "Overflow";
        }
        return "Unknown";
    }
};
inline std::error_code make_error_code(parse_error e) noexcept {
    static parse_category category;
    return std::error_code(static_cast<int>(e), category);
}
namespace std {
template <>
struct is_error_code_enum<parse_error> : true_type {};
}
std::optional<int> safe_atoi(const std::string& str, std::error_code& ec) noexcept {
    int value{};
    auto result = std::from_chars(str.data(), str.data() + str.size(), value);
    if (result.ec == std::errc()) {
        ec = parse_error::success;
        return value;
    }
    ec = parse_error::not_integer;
    return std::nullopt;
}
// ===========================
// 2. Processor 回调机制
// ===========================
struct device_processor {};
struct packet_processor {};
struct device_processor_begin { device_processor& processor; };
struct packet_processor_begin { packet_processor& processor; };
struct device_processor_end : device_processor_begin {
    std::error_code ec;
    std::exception_ptr ex;
    device_processor* which;
    device_processor_end(device_processor& p) : device_processor_begin{p}, which(&p) {}
};
struct packet_processor_end : packet_processor_begin {
    std::exception_ptr ex;
    packet_processor* which;
    packet_processor_end(packet_processor& p) : packet_processor_begin{p}, which(&p) {}
};
struct processor_manager_callback {
    virtual void on(const device_processor_begin& e) = 0;
    virtual void on(const packet_processor_begin& e) = 0;
    virtual void on(const device_processor_end& e) = 0;
    virtual void on(const packet_processor_end& e) = 0;
};
// ===========================
// 3. Processor Manager
// ===========================
struct processor_manager_settings {};
struct processor_manager {
    std::vector<device_processor> devices;
    std::vector<packet_processor> packets;
    processor_manager_callback* cb{nullptr};
    explicit processor_manager(const processor_manager_settings&) {}
    void add_device(device_processor& d) { devices.push_back(d); }
    void add_feed(packet_processor& p) { packets.push_back(p); }
    void start() {}
    void stop() noexcept {}
    void subscribe(processor_manager_callback& callback) { cb = &callback; }
    void process() {
        if (!cb) return;
        // 模拟设备处理
        for (auto& d : devices) {
            cb->on(device_processor_begin{d});
            device_processor_end e(d);
            try {
                // 模拟可能的错误
                std::error_code ec;
                auto v = safe_atoi("abc", ec);
                e.ec = ec;
                if (!v) throw std::system_error(ec, "Device parse error");
            } catch (...) {
                e.ex = std::current_exception();
            }
            cb->on(e);
        }
        // 模拟 packet 处理
        for (auto& p : packets) {
            cb->on(packet_processor_begin{p});
            packet_processor_end e(p);
            try {
                // 模拟处理
            } catch (...) {
                e.ex = std::current_exception();
            }
            cb->on(e);
        }
    }
};
// ===========================
// 4. Logging / Callback 实现
// ===========================
struct logging_callback : processor_manager_callback {
    void on(const device_processor_begin& e) override {
        std::cout << "[LOG] Device begin\n";
    }
    void on(const packet_processor_begin& e) override {
        std::cout << "[LOG] Packet begin\n";
    }
    void on(const device_processor_end& e) override {
        std::cout << "[LOG] Device end, error: " << e.ec.message() << "\n";
        if (e.ex) {
            try { std::rethrow_exception(e.ex); }
            catch (const std::system_error& se) { std::cout << "Exception: " << se.what() << "\n"; }
        }
    }
    void on(const packet_processor_end& e) override {
        std::cout << "[LOG] Packet end\n";
        if (e.ex) { try { std::rethrow_exception(e.ex); } catch(...) {} }
    }
};
// ===========================
// 5. Multi-threaded example
// ===========================
struct thread_pool {
    std::list<std::thread> threads;
    std::mutex m_;
    std::exception_ptr ex_;
    template <typename Func>
    void run(Func f, unsigned count) {
        for (unsigned i = 0; i < count; ++i) {
            threads.emplace_back([&, i](){
                try { f(i); }
                catch (...) {
                    std::lock_guard<std::mutex> g(m_);
                    ex_ = std::current_exception();
                }
            });
        }
        for (auto& t : threads) t.join();
        if (ex_) std::rethrow_exception(ex_);
    }
};
// ===========================
// 6. Main
// ===========================
int main() {
    processor_manager_settings settings;
    processor_manager pm(settings);
    device_processor d1;
    packet_processor p1;
    pm.add_device(d1);
    pm.add_feed(p1);
    logging_callback cb;
    pm.subscribe(cb);
    std::cout << "=== Single-threaded processing ===\n";
    pm.process();
    std::cout << "\n=== Multi-threaded processing ===\n";
    thread_pool pool;
    pool.run([&](unsigned id){ pm.process(); }, 2);
    return 0;
}

示例特点总结

  1. 整数解析
    • 使用 std::from_chars + std::error_code 返回明确错误
    • 可区分非整数、溢出等情况
  2. Processor Manager 回调
    • 处理开始/结束事件
    • 将错误信息通过 std::error_codestd::exception_ptr 传递
  3. 异常处理
    • 使用 std::system_error 包装 std::error_code
    • 回调可以选择捕获并打印
  4. 日志/警告机制
    • 日志通过 processor_manager_callback 独立处理
    • 与业务逻辑解耦
  5. 多线程
    • 线程异常通过 std::exception_ptr 收集
    • 主线程统一重新抛出
      这个示例已经把你之前讲的整数解析、错误码、异常、回调、多线程、日志都整合起来,是一个完整端到端的实现。
=== Single-threaded processing ===
[LOG] Device begin
[LOG] Device end, error: Not an integer
Exception: Device parse error: Not an integer
[LOG] Packet begin
[LOG] Packet end
=== Multi-threaded processing ===
[LOG] Device begin
[LOG] Device end, error: Not an integer
Exception: Device parse error: Not an integer
[LOG] Packet begin
[LOG] Packet end
[LOG] Device begin
[LOG] Device end, error: Not an integer
Exception: Device parse error: Not an integer
[LOG] Packet begin
[LOG] Packet end
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐