Skip to content

[Refactor] Remove the ios_defs::failure Exception Class #10

@liwei-cpp

Description

@liwei-cpp

🌐 Language


[Refactor] 移除 ios_defs::failure 异常类

背景

当前 io_base.h 中定义了一个 ios_defs::failure 异常类(io_base.h:47-65):

class failure : public std::runtime_error
{
public:
    explicit failure(const std::string& msg, const std::error_code& ec = std::io_errc::stream)
        : std::runtime_error(msg)
        , m_ec(ec) {}

    explicit failure(const char* msg, const std::error_code& ec = std::io_errc::stream)
        : std::runtime_error(msg)
        , m_ec(ec) {}

    virtual const std::error_code& code() const noexcept
    {
        return m_ec;
    }

private:
    std::error_code m_ec;
};

该异常类在整个代码库中仅在一处被抛出——io_state_and_exp::clear() 的末尾(io_base.h:112-113):

if (need_throw_exp)
    throw ios_defs::failure("failure bit has been set");

问题

ios_defs::failure 的设计存在以下问题:

  1. 丢失原始错误信息:IOv2 的异常体系已经在 handle_exception() 中将原始异常按类型分类存储(m_exp_dev_failm_exp_cvt_failm_exp_str_failm_exp_other_fail)。clear() 中的逻辑也会优先尝试重新抛出原始异常(io_base.h:85-104)。ios_defs::failure 仅在存储的原始异常指针为空时作为兜底被抛出,此时它携带的只是一个泛化的 "failure bit has been set" 消息,丢失了所有错误上下文。

  2. 与 IOv2 异常体系不一致:IOv2 自身已有明确分层的异常类型(device_errorcvt_errorstream_erroreof_error),分别对应不同的 fail bit。ios_defs::failure 不属于这个体系,它是从 std::runtime_error 继承的,调用方无法通过捕获 IOv2 的异常类型来处理它。

  3. 仅为兼容 std::ios_base::failure 而存在:这个类是对 C++ 标准库 std::ios_base::failure 的模仿。但 IOv2 已经建立了自己独立的、更细粒度的异常和状态体系,不需要保留这个标准库兼容层。

  4. std::error_code 未被使用failure 类中携带的 m_ecstd::error_code)在唯一的抛出点使用的是默认值 std::io_errc::stream,没有任何地方传递具体的错误码,也没有任何捕获点使用 code() 方法。

解决方案

移除 ios_defs::failure 异常类,改为在 clear() 中直接抛出与 fail bit 对应的 IOv2 异常类型:

重构前:

if (need_throw_exp)
    throw ios_defs::failure("failure bit has been set");

重构后(示例):

if (state_in_exp & ios_defs::devfailbit)
    throw device_error("device failure bit has been set");
else if (state_in_exp & ios_defs::cvtfailbit)
    throw cvt_error("converter failure bit has been set");
else if (state_in_exp & ios_defs::strfailbit)
    throw stream_error("stream failure bit has been set");
else if (state_in_exp & ios_defs::otherfailbit)
    throw stream_error("other failure bit has been set");

这样,即使原始异常指针为空,调用方仍可以通过捕获 IOv2 的标准异常类型来正确处理错误,且不会丢失错误的分类信息。

任务列表

  • 重构 io_state_and_exp::clear() 中的兜底抛出逻辑,将 throw ios_defs::failure(...) 替换为根据 fail bit 类型抛出对应的 IOv2 异常。
  • 移除 ios_defs::failure 类定义(io_base.h:47-65)。
  • 移除 #include <ios> 的依赖(如果移除 failure 后不再需要 std::io_errc)。
  • 更新或移除 IOv2Test/io/io_base/test_io_base_failure.cpp 中与 ios_defs::failure 相关的测试用例。
  • 更新 IOv2Test/io/io_state_and_exp/test_io_state_and_exp.cpp 中捕获 ios_defs::failure 的测试代码。
  • 运行全部 I/O 相关测试,确保异常行为正确(回归测试)。

[Refactor] Remove the ios_defs::failure Exception Class

Background

io_base.h currently defines an ios_defs::failure exception class (io_base.h:47-65):

class failure : public std::runtime_error
{
public:
    explicit failure(const std::string& msg, const std::error_code& ec = std::io_errc::stream)
        : std::runtime_error(msg)
        , m_ec(ec) {}

    explicit failure(const char* msg, const std::error_code& ec = std::io_errc::stream)
        : std::runtime_error(msg)
        , m_ec(ec) {}

    virtual const std::error_code& code() const noexcept
    {
        return m_ec;
    }

private:
    std::error_code m_ec;
};

This exception class is only thrown in one place in the entire codebase — at the end of io_state_and_exp::clear() (io_base.h:112-113):

if (need_throw_exp)
    throw ios_defs::failure("failure bit has been set");

Problem

ios_defs::failure has the following issues:

  1. Loses original error context: IOv2's exception system already categorizes and stores original exceptions by type in handle_exception() (m_exp_dev_fail, m_exp_cvt_fail, m_exp_str_fail, m_exp_other_fail). The logic in clear() prioritizes rethrowing the original exception (io_base.h:85-104). ios_defs::failure is only thrown as a fallback when the stored exception pointer is null, carrying only a generic "failure bit has been set" message with all error context lost.

  2. Inconsistent with IOv2's exception hierarchy: IOv2 already has clearly layered exception types (device_error, cvt_error, stream_error, eof_error), each corresponding to a specific fail bit. ios_defs::failure does not belong to this hierarchy — it inherits from std::runtime_error, and callers cannot handle it by catching IOv2's exception types.

  3. Exists only for std::ios_base::failure compatibility: This class mimics C++ standard library's std::ios_base::failure. But IOv2 has already established its own independent, more fine-grained exception and state system, making this compatibility layer unnecessary.

  4. std::error_code is unused: The m_ec member in failure is always set to the default std::io_errc::stream at the only throw site. No call site passes a specific error code, and no catch site uses the code() method.

Proposed Solution

Remove the ios_defs::failure exception class and instead throw the appropriate IOv2 exception type corresponding to the fail bit in clear():

Before:

if (need_throw_exp)
    throw ios_defs::failure("failure bit has been set");

After (example):

if (state_in_exp & ios_defs::devfailbit)
    throw device_error("device failure bit has been set");
else if (state_in_exp & ios_defs::cvtfailbit)
    throw cvt_error("converter failure bit has been set");
else if (state_in_exp & ios_defs::strfailbit)
    throw stream_error("stream failure bit has been set");
else if (state_in_exp & ios_defs::otherfailbit)
    throw stream_error("other failure bit has been set");

This way, even when the original exception pointer is null, callers can still correctly handle errors by catching IOv2's standard exception types, without losing the error classification.

Action Items

  • Refactor the fallback throw logic in io_state_and_exp::clear(), replacing throw ios_defs::failure(...) with fail-bit-specific IOv2 exceptions.
  • Remove the ios_defs::failure class definition (io_base.h:47-65).
  • Remove the #include <ios> dependency if std::io_errc is no longer needed after removing failure.
  • Update or remove test cases related to ios_defs::failure in IOv2Test/io/io_base/test_io_base_failure.cpp.
  • Update IOv2Test/io/io_state_and_exp/test_io_state_and_exp.cpp where ios_defs::failure is caught.
  • Run all I/O-related tests to ensure correct exception behavior (regression testing).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions