【问题标题】:deriving from std::exception源自 std::exception
【发布时间】:2011-01-31 23:15:09
【问题描述】:

我想从 std::exception 派生以将特定信息添加到我的日志文件中,但我不知道如何从 std::exception 访问 .what()。

此外,我知道在我的异常处理程序中创建字符串是不安全的,但我不是该主题的专家,那么有哪些更安全的替代方案?

struct Exception : public std::exception, private boost::noncopyable
{
    public:
        Exception(std::string msg)
            : message(msg)
        {}
        ~Exception()
        {}

        virtual const char* what() const throw 
        {
            std::string what = message + // and now what? base.what()
            LOG(what); // write to log file
            return what.c_str();
        }

    private:
        std::string message;
};

编辑: 我真的问错了我的问题。我对安全性很感兴趣,我只是认为有更多的数据用于记录会很好。我错了。

现在,如果之前有 bad_alloc,我不会对消息字符串抛出 bad_alloc 过于偏执,我宁愿有一个简洁的消息。话虽如此,我重写了一些东西:

struct Exception : public std::exception
{
    public:
        Exception(std::string msg)
            : message(msg)
        {}
        ~Exception()
        {}

        virtual const char* what() const throw 
        {
            LOG(what); // write to log file
            return what.c_str();
        }

    private:
        std::string message;
};

现在对该代码还有什么大问题吗? LOG() 抛出 std::exception 以防万一出现问题,因为我不希望派生异常类调用日志的无限循环,并且该类再次调用 log 会再次导致相同的异常。 这会像我想要的那样工作吗,或者派生类调用中的日志记录异常终止()或导致核心转储?

【问题讨论】:

  • 回复:更安全的替代品。这是一个很大的问题。适合它自己的问题。
  • 欢迎来到 Stack Overflow。您似乎在问 两个 问题(这意味着您应该发表两个单独的帖子)。一个询问如何从基类调用继承的方法,另一个询问如何在没有字符串的情况下存储异常消息(大概是因为您不想分配内存并招致bad_alloc 的风险)。两者都与特别是从std::exception 派生无关。单独的具体问题会导致更具描述性的问题标题和更有用的答案。请考虑拆分此问题。
  • 如果您的代码抛出 Exception("...") 类型的异常,您期望 std::exception::what() 返回什么?我认为它不会包含任何有用的东西(如果它包含任何东西)。我建议简单地返回 message.c_str()。
  • return what.c_str() 语句将成为一个问题,因为 'what' 将超出范围并被破坏。你将返回一个错误的指针。
  • @Kos: std::exception::what() 是 虚拟的。至少它在 VC++ 中。

标签: c++ exception-handling


【解决方案1】:

编辑:自从写下这个答案后,我偶然发现了 Boost 文档中的 Error and Exception Handling 部分。我会推荐该文档而不是此答案。


首先,使您的异常不可复制是一个坏主意。当你写诸如

之类的东西时
// could be any exception, doesn't matter.
throw Exception(...);

运行时会将该对象的副本创建到一个特殊位置。一些编译器可能会对此进行优化并在该位置创建原始对象,但The C++ Programming Language 表示它是一个副本,我也相信这是标准所说的,尽管我不确定。在您当前的环境中,您可能会侥幸逃脱,但情况可能并非总是如此。

那么,其他一切都取决于你对极端情况的偏执程度。

内存分配部分在异常子句(即构造函数)中大多是不稳定的。如果此内存分配碰巧失败(即抛出std::bad_alloc),则有两种可能性,具体取决于您如何编写throw 语句:

  1. std::string 是在 throw 语句之前创建的,std::bad_alloc 替换了你认为你会提出的异常,问题有点糟糕。
  2. std::string 在构造函数调用中内联创建。如果这被标准视为“在异常处理期间”,则将调用 std::unexpected()/std::terminate() 并且您基本上会获得核心转储。

无论如何,报告错误似乎都达不到预期的效果。

我总是建议在构造函数中创建某种不分配内存的临时状态,并等待对std::what() 的调用以创建报告错误的字符串,但这仍可能导致案例#1。您可以使用一些编译时确定的缓冲区大小来确保不会发生这种情况。

很多人会告诉你,他们在构造函数中分配字符串从来没有遇到过问题,因为除非最初的异常是 std::bad_alloc,否则不太可能引发 std::bad_alloc。因此,这取决于您的偏执程度。

【讨论】:

    【解决方案2】:

    我不会深入讨论您的代码存在的无数问题,因为它是一个巨大的蠕虫罐头。但这是调用基类方法的方式:

    std::exception::what()
    

    【讨论】:

    • 顺便说一句,我更改了你的局部变量的名称。
    • 返回值与返回类型不匹配?
    • 您不认为将日志记录作为what() 的副作用是一个坏主意吗?您可能不会获得任何日志记录,或者您可能会多次记录相同的消息;日志记录也有可能尝试抛出自己的异常。
    • 使用静态成员存储返回值可能会导致多线程程序中难以跟踪的错误。
    • @Mark Ransom,我的 LOG() 函数确实抛出了 std::exception,而不是自己的基类,因为我认为这会阻止我的派生异常的无限调用,我错了吗?跨度>
    【解决方案3】:

    这个问题的答案是,除非您明确设置 what() 应该从 std::exception 返回的值,否则您不想调用它。事实是,它的行为方式可能出乎你的意料,并且在不同的实现中会有所不同。

    请注意,该标准在 std::exception 中没有提供任何功能来提供开头的字符串值。那些通过调用 std::exception::what() 实际上提供有用信息的实现为类添加了额外的非标准功能。例如,MSVC 有一个exception(char const* const&) 构造函数。这不在标准中,您可能不想依赖它。

    最好不要从派生类调用 std::exception::what。当然,在您的自定义版本中调用 std::exception 下的子类,但不要在直接派生中这样做。

    如果你坚持直接调用这个函数,那么你最好检查一下 NULL,因为这就是你可能会得到的。

    【讨论】:

    • +1 很好的答案,但你没有回答他的实际问题,这与异常无关。如何调用基类方法?
    • 谢谢诺亚,@john,我想我真的问错了问题。我对安全性很感兴趣,我只是想我可以从基类中获取更多数据来记录日志。看来我完全错了
    【解决方案4】:

    第二部分。我有数百个 kloc 在 >100 个平台上运行,这些平台具有带有 std::string 成员的 std::exception 派生类。从来没有遇到过问题

    class myex: public std::exception
    {
    public:
        std::string m_msg;
        std::string m_className;
        int m_rc;
    ...
    

    【讨论】:

      猜你喜欢
      • 2015-04-17
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 2010-12-06
      • 1970-01-01
      • 2011-02-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多