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

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

本文关键字:what own std exception Does      更新时间:2023-10-16

我正在从std::system_error派生我自己的异常,称之为MyException,并覆盖了what()来计算和返回我的消息。 MyException 的初始值设定项列表不会调用接受消息的 system_error 构造函数重写。

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

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

我假设这将使MyExceptionstd::exception副本能够返回有效的what()。尽管我会受到性能打击,因为每次创建新的MyExceptions时都需要计算"什么";我不能只在第一次调用 what(( 时才懒惰地计算它。

我有点担心"what"字符串的所有权,因为what()返回的是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;
    }

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

是的,这是由标准保证的。

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

runtime_error(const string& what_arg);

效果:构造类 runtime_error .
的对象后置条件: strcmp(what(), what_arg.c_str()) == 0 .

runtime_error(const char* what_arg);

效果:构造类 runtime_error .
的对象后置条件: strcmp(what(), what_arg) == 0 .

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

接下来是 [异常]p2:

派生自类exception的每个标准库类T都应具有可公开访问的复制构造函数和一个可公开访问的复制赋值运算符,这些运算符不会因异常而退出。这些成员函数应满足以下后置条件:如果两个对象lhsrhs都具有动态类型T并且lhsrhs的副本,则strcmp(lhs.what(), rhs.what())应等于0

因此,必须

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

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

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

我假设这将使MyExceptionstd::exception副本能够返回有效的what()

不!当你制作MyExceptionstd::exception副本时,你正在将对象切成一个比what的值物理存储位置更少的派生类型。如果必须创建异常的副本,则可以使用的最小派生类型是 std::runtime_error 。(当然,您始终可以安全地对MyException进行std::exception引用。换句话说,永远不可能从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());
}

我会说编写一个分配的异常构造函数(出于显而易见的原因(的形式很糟糕,但考虑到我知道的每个当前标准库实现都对std::basic_string<>使用短字符串优化,这在实践中极不可能成为问题。

您的问题与了解异常的生命周期有关。 这个问题在这里和这里的帖子中讨论过,可能会有所帮助。

您可以保证使用智能指针延长异常的生存期。 我不确定性能影响是什么,但你可能会使用它来坚持你自己的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 上读到的使用异常指针的示例的重写,但我使用了您的异常类而不是像 std::logic_error 这样的标准异常。

至于你原来的问题,似乎很难做出硬保证。 我确实在赋值运算符的文档中找到了适用于 C++11 的异常的以下语句。 在 C++98 中,甚至不提供此保证。

C++标准库(包括 this(中的每个异常至少都有一个复制赋值运算符重载,当动态类型匹配时,该运算符重载保留成员返回的字符串表示形式。

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

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

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

请注意,引发

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

相关文章: