如何避免将std :: string放在异常类中?

时间:2012-02-22 22:08:49

标签: c++ exception exception-handling

这是我的异常类:

class Win32Failure : public std::exception
{
public:
    Win32Failure( char const* win32_function_name, LONG error_code );

    char const* win32_function_name() const { return win32_function_name_; }
    LONG error_code() const { return error_code_; }

    virtual char const* what() const;

private:

    std::string GetFormattedMessage() const;

    char const* win32_function_name_;
    LONG error_code_;
    std::string error_text_;
};

Win32Failure::Win32Failure( char const* win32_function_name, LONG error_code )
    : error_code_(error_code)
    , win32_function_name_(win32_function_name)
{
    std::stringstream error_msg;
    error_msg   << win32_function_name << " failed with code: "
                << error_code << " (" << GetFormattedMessage() << ")"
                ;

    error_text_ = error_msg.str();
}

std::string Win32Failure::GetFormattedMessage() const
{
    TCHAR message_buffer[1000];

    FormatMessage(
        //FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        error_code_,
        0, // Default language
        reinterpret_cast<LPTSTR>(&message_buffer),
        sizeof(message_buffer) / sizeof(TCHAR),
        NULL
        );

    return std::string(message_buffer);
}

char const* Win32Failure::what() const
{
    return error_text_.c_str();
}

boost exception guidelines建议不要将任何分配内存的对象放置为我的异常类的成员。在这种情况下,std::string的使用违反了这一点。我尊重这个规则,但是我想不出一种方法来实现what()覆盖而不使用std :: string来管理内存(而不是要求调用者为我管理它)。

我可以使用固定大小的缓冲区作为成员并使用C库函数(如snprintf())来完成这项工作,但这不是C ++的惯用语,因此不是理想的解决方案。

这是一个合适的异常类实现吗?如果没有,可以做出哪些改进?

3 个答案:

答案 0 :(得分:6)

对于它的价值,<stdexcept>中定义的所有异常类型都将std::string作为参数。图书馆设计师可以解释这是“好的”#34;我认为反对这一点的主要论点是,如果您处于内存受限的环境中,您可能无法分配内存来抛出异常。

答案 1 :(得分:2)

例外情况应防止在施工期间可能造成的资源枯竭。在异常中使用动态数组并不好(除非,它是通过指针完成的,如果分配失败,则会有一个回退计划)。使用std::string向用户传达一些信息是双重的:首先,它是动态数组,其次是大多数没用 - 抛出异常的函数不知道情况,试图合理地解释它失败的原因。

异常应该做有用的事情,比如堆栈跟踪+函数参数恢复(如果可能)+ ID到外部消息(例如动态库资源),以防最终用户需要格式化的低级消息。如果异常对最终用户产生,它应该以来自外部字符串表模板的相干格式化错误消息的形式(可能添加一些运行时参数)。如果异常用于帮助调试并将其发送给开发人员,那么堆栈跟踪,函数参数,机器状态是有用的,一些硬编码的通用字符串不是。

编辑:看起来你正试图围绕Windows API调用制作C ++异常包装器,我是否正确?如果是这样,你应该考虑几件事:

  • 使用_set_se_translator()来处理类似于C ++异常的SE;
  • 使用dbghelp.dll的{​​{3}},StackWalk64()和类似的函数来生成人类可读的堆栈跟踪(只有地址在异常构造函数中用于调试异地);
  • 使用内联通用包装器检查由Windows API返回的错误情况,该错误情况会在指定条件下引发异常或将返回的值转发为其返回类型。只需确保没有开销(模板+内联+右值引用完全消除了开销;另外,请确保不要直接从checker函数抛出异常,将其委托给非内联函数,以避免显式抛出异常的函数的编译器开销)

答案 2 :(得分:0)

作为类私有数据的一部分有一个静态字符缓冲区(如果是多线程则考虑使用TLS)

what()只返回一个指向此缓冲区的const指针。

在GetFormattedMessage()中填写缓冲区(就像你现在一样) (如果需要,可能会从unicode / wchars转换,因为你正在处理TCHAR)

作为静态类成员,在发生任何异常之前,缓冲区在堆栈上预先分配。

您不需要管理在堆栈上分配的内存。 但是你会嚼掉1000个字节的堆栈空间(通常不是什么大不了的事)

但是只有一个缓冲区来保存错误文本,但在任何给定时间只应存在一个自定义异常(我认为?)