std::exception 是否拥有 `what()`?

Does std::exception own `what()`?

我从 std::system_error 导出我自己的异常,将其称为 MyException 并覆盖 what() 来计算和 return 我的消息。 MyException 的初始化列表不调用接收消息的 system_error 构造函数覆盖。

如果我捕获 MyException 并将其复制到 std::exception,则在 std::exception 上调用 what() 的结果是 nullptr。这是有道理的。

我的问题是,如果我确实使用 system_exception 的构造函数在初始化 MyException 时获取消息,是否指定 system_error 将获取消息的副本并且拥有并释放它?

我假设这将使 MyExceptionstd::exception 副本能够 return 成为有效的 what()。尽管每次创建新的 MyExceptions 时 'what' 都需要计算,但我会对性能造成影响;我不能只在第一次调用 what() 时懒惰地计算它。

我有点担心 'what' 字符串的所有权,因为 what() return 是 char* 而不是 const std::string&.

代码大概是这样的(我没编译过):

    class MyException : public std::system_error
    {
        std::string what_;
    public:
        MyException(int errorValue, const std::error_category& category)
            : std::system_error(errorValue, category)
        {}

        char* what() const
        {
           what_ = "MyException: " + to_string(code().value());
           return what_.c_str();
        }
    };

    int main()
    {
        std::exception ex;

        try
        {
            throw MyException(4, system_category());
        }
        catch( const MyException& e )
        {
            ex = e;
        }

        printf("what= %s", ex.what());

        return 1;
    }

class 异常不拥有任何字符串。当你切片你的异常对象时,你会得到一个基本的异常对象,它没有被覆盖的 what() 虚函数。

what() 函数的魔力在于虚函数 what() 以及您的派生函数 class。您可以将存储在静态内存中的 const char * 传递给异常对象,它不会被复制。

请注意,引发异常时对象的副本可能会引发新的异常,因此不推荐这样做(例如,在 bad_alloc 之后,您可能无法创建新的字符串对象)。这就是为什么通过引用而不是值更好地捕获异常的原因。

您的问题与理解异常的生命周期有关。帖子 here and here 中讨论了这个问题,可能会有帮助。

您可以保证使用智能指针延长异常的生命周期。我不确定对性能的影响是什么,但是您可以使用它来保留您自己的 std::system_error 扩展并完全避免复制构造。 (实际上,我不保证可以避免复制构造。看起来智能指针的创建可能会也可能不会复制异常。但它会复制您的异常,如果您提供复制构造函数,它应该做正确的事情你应该提供。)你的主要功能最终会看起来更像这样。

#include <exception> // std::exception_ptr

int main()
{
    std::exception_ptr p;

    try
    {
        throw MyException(4, system_category());
    }
    catch( const MyException& e )
    {
        p = std::current_exception();
    }

    try
    {
        std::rethrow_exception(p);
    }
    catch (const std::exception& e)
    {
        printf("what= %s", e.what());
    }

    return 1;
}

这基本上只是对我在 cplusplus.com here 上读到的使用异常指针示例的重写,但我使用了您的异常 class 而不是标准异常,如 std::logic_error.

关于你原来的问题,好像很难保证。我确实在适用于 C++11 的异常赋值运算符文档中找到了以下语句。在 C++98 中甚至没有提供这种保证。

Every exception within the C++ standard library (including this) has, at least, a copy assignment operator overload that preserves the string representation returned by member what when the dynamic types match.

但是,std::system_error 的动态类型与您的情况下 std::exception 的动态类型不匹配,所以我认为它不能保证有效。

My question is, if I do use the constructor of system_exception that takes a message when initializing MyException, is it specified that system_error will take a copy of the message and own it and free it?

是的,这是标准保证的。

首先,std::exception 不拥有 whatstd::runtime_error 拥有。 std::runtime_error 的构造函数是这样定义的 ([runtime.error]p2-5):

runtime_error(const string& what_arg);

Effects: Constructs an object of class runtime_error.
Postcondition: strcmp(what(), what_arg.c_str()) == 0.

runtime_error(const char* what_arg);

Effects: Constructs an object of class runtime_error.
Postcondition: strcmp(what(), what_arg) == 0.

因此,它必须在内部存储 what_argcopy,因为对传入值的生命周期没有要求。

接下来是[异常]p2:

Each standard library class T that derives from class exception shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception. These member functions shall meet the following postcondition: If two objects lhs and rhs both have dynamic type T and lhs is a copy of rhs, then strcmp(lhs.what(), rhs.what()) shall equal 0.

因此,必须有一个复制构造函数,它绝不能抛出,并且副本必须为 what() 保持相同的 return 值。对于复制赋值运算符也是如此。

综上所述,我们可以推测 std::runtime_error 必须在引用计数字符串内部保留您为 what_arg 传递的值(以避免复制时发生分配异常),并且该值无论复制 and/or 切片如何,都会持续存在 – 但仅下降到 std::runtime_error 而不是 下降到 std::exception ! (有关 what 存储的基本原理和要求的更多信息可以在 @HowardHinnant: 的这个非常有趣的答案中找到)

std::system_error 继承自 std::runtime_error,因此对于它和从它派生的任何类型都适用(只要派生类型保持非抛出复制构造函数不变)。

I'm assuming this would enable a std::exception copy of MyException to be able to return a valid what().

没有!当您对 MyException 进行 std::exception 复制 时,您 slicing 对象的派生类型低于 what' s 值是物理存储的。如果您必须 复制您的异常,您可以使用的最少派生类型是std::runtime_error。 (当然,您始终可以安全地对 MyException 进行 std::exception 引用 。)换句话说,它是 never 可以从 std::exception 对象 what().

中获取有意义的字符串

此代码具有您想要的可移植行为:

#include <cstdio>
#include <stdexcept>
#include <system_error>
#include <string>

class MyException : public std::system_error {
public:
    MyException(int errorValue, std::error_category const& category)
      : std::system_error(
            errorValue, category,
            "MyException: " + std::to_string(errorValue)
        )
    { }
};

int main() {
    std::runtime_error ex;

    try {
        throw MyException(4, system_category());
    } catch(MyException const& e) {
        ex = e;
    }

    std::printf("what= %s", ex.what());
}

我会说编写分配的异常构造函数是一种糟糕的形式(出于明显的原因),但考虑到我所知道的每个当前标准库实现都使用 short-string optimization for std::basic_string<>,这在实践中极不可能成为问题。