来源于std::exception的派生类

7
我希望通过继承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异常感到如此偏执,我宁愿有一个整洁的消息。话虽如此,我重写了一些东西:
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异常,因为我不希望派生异常类无限循环调用日志,而导致再次出现相同的异常。 这样做会按照我想要的方式运作吗?或者在我的派生类中记录异常时,会调用terminate()或导致核心转储?


2
回复:更安全的替代方案。这是一个很大的问题,适合单独提出来讨论。 - John Dibling
1
欢迎来到Stack Overflow。看起来你正在询问两个问题(这意味着你应该发布两个单独的帖子)。一个问题是如何从基类调用继承的方法,另一个问题是如何存储异常消息而不是字符串(可能是因为你不想分配内存并承担bad_alloc的风险)。两者都与特定于派生自std::exception没有任何关系。分开提出具体问题会导致更具描述性的问题标题和更有用的答案。请考虑拆分此问题。 - Rob Kennedy
2
如果你的代码抛出了 Exception 类型的异常("..."), 你期望 std::exception::what() 函数返回什么?我认为它可能不会包含任何有用的信息(如果它根本包含任何信息的话)。我建议简单地返回 message.c_str()。 - Ferruccio
2
由于'what'将超出作用域并被释放,因此'return what.c_str()'语句会成为一个问题。你将返回一个错误的指针。 - Ferruccio
2
@Kos:std::exception::what()是虚函数。至少在VC++中是这样的。 - Ferruccio
显示剩余5条评论
4个回答

9

编辑:自从我写下这个答案,我偶然发现了Boost文档中的错误和异常处理部分。我建议阅读那篇文档而不是这个答案。


首先,让你的异常不可复制是一个坏主意。当你写出像下面这样的代码时:

// could be any exception, doesn't matter.
throw Exception(...);

运行时会将该对象的副本创建到一个特殊位置。一些编译器可能会对此进行优化并在该位置创建原始对象,但是《C++程序设计语言》表示这是一个副本,我也相信标准也是这样规定的,尽管我不确定。您可能可以在当前环境中使用它,但这可能并非总是如此。
然后,其他所有内容都取决于您对边角情况的偏执程度。
内存分配部分在异常子句(即构造函数)中大多数都是不稳定的。如果此内存分配失败(即抛出std::bad_alloc),则取决于您如何编写throw语句,有两种可能性:
  1. throw语句之前创建了std::stringstd::bad_alloc替换了您认为会引发的异常,问题报告不太好。
  2. std::string在构造函数调用中内联创建。如果标准将其视为“异常处理期间”,则会调用std::unexpected()/std::terminate(),您基本上会得到核心转储。
无论哪种情况,似乎都无法获得所需的错误报告效果。
我总是建议创建某种临时状态,该状态在构造函数中不分配内存,并等待调用std::what()以创建报告错误的字符串,但这仍可能导致情况1。您可以采用一些编译时确定的缓冲区大小来确保不会发生这种情况。
许多人会告诉您,他们从未遇到过在构造函数中分配字符串的问题,因为除非最初的异常本身就是std::bad_alloc,否则不太可能引发它。因此,这取决于您的偏执程度。

4

我不会详细讨论您的代码存在的各种问题,因为这是一个棘手的问题。但是以下是如何调用基类方法的方式:

std::exception::what()

1
返回值与返回类型不匹配? - UncleBens
2
你不觉得在what()的副作用中记录日志是一个坏主意吗?很有可能你根本就不会得到任何日志记录,或者你可能会多次记录相同的消息;还有可能记录日志时会尝试抛出自己的异常。 - Mark Ransom
1
在多线程程序中,使用静态成员来存储返回值可能会导致难以跟踪的错误。 - Sjoerd
@Noah,@Mark,@Sjoerd:我不反对你们中的任何一个人。正如我在对OP的评论中所说,如何安全地做到这一点是一个更大的问题,值得单独发帖讨论。我在这里所做的只是展示如何调用基类方法。我将编辑我的回复,仅显示那个部分。 - John Dibling
1
@Mark Ransom,我的LOG()函数确实会抛出std::exception,但不是通过自己的基类,因为我认为这样可以防止无限调用我的派生异常,这样做有问题吗? - cppanda
显示剩余3条评论

3
除非您已明确设置了std::exception中what()应返回的值,否则您不应该调用它。事实是它会以您可能意想不到的方式行事,并且在不同的实现上表现不同。
请注意,标准未提供std::exception中提供字符串值的功能。那些实际从调用std::exception::what()中提供有用信息的实现会向类添加额外的非标准功能。例如,MSVC具有一个exception(char const* const&)构造函数。这不在标准中,您可能不希望依赖它。
更好的方法是从派生类中永远不要调用std::exception::what。当然,在子类化std::exception下的东西中进行upcalls,但不要在直接派生类中执行它们。
如果您坚持直接调用此函数,则最好检查是否为空,因为您可能会得到NULL。

+1 你的回答很好,但是你没有回答他实际问题,与异常无关。如何调用基类方法? - John Dibling
谢谢Noah,@John,我想我把问题问反了。我更关心安全性,只是认为我可以从基类中获得更多的日志记录数据。看来我完全错了。 - cppanda

-1

至于第二部分,我有几百个 kloc 运行在超过 100 个平台上,其中有一个 std::exception 派生类,具有 std::string 成员。从未遇到任何问题。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接