Does std::exception own `what()`?
Does std::exception own `what()`?
我正在从std::system_error
派生我自己的异常,称之为MyException
,并覆盖了what()
来计算和返回我的消息。 MyException
的初始值设定项列表不会调用接受消息的 system_error 构造函数重写。
如果我捕获MyException
并将其复制到std::exception
则在std::exception
上调用what()
的结果是nullptr
。这是有道理的。
我的问题是,如果我确实使用在初始化MyException
时获取消息的system_exception构造函数,是否指定system_error将获取消息的副本并拥有它并释放它?
我假设这将使MyException
的std::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
都应具有可公开访问的复制构造函数和一个可公开访问的复制赋值运算符,这些运算符不会因异常而退出。这些成员函数应满足以下后置条件:如果两个对象lhs
和rhs
都具有动态类型T
并且lhs
是rhs
的副本,则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
,因此对于它和从它派生的任何类型都同样适用(只要派生类型保持非抛出复制构造函数不变性(。
我假设这将使
MyException
的std::exception
副本能够返回有效的what()
。
不!当你制作MyException
的std::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之后,可能无法创建新的字符串对象(。这就是为什么通过引用而不是按值更好地捕获异常的原因。
- 多个文件的内存分配错误"在抛出 'std :: bad_alloc' what (): std :: bad_alloc 的实例后终止调用" [C++]
- 什么是 std::exception::what() 以及为什么要使用它?
- what(): basic_string::_M_construct null not valid
- what(): basic_string::_M_construct 空无效错误
- What is unordered_set in C++
- 在抛出 'std::runtime_error' 的实例后终止调用 what(): Filebuf 和 ostream 的 I/O 错误
- 是否可以创建一个用户定义的文本,将字符串文本转换为 own 类型的数组?
- ESP32-AsyncUDP own handler function for udp.onPacket
- 自定义runtime_error,如果我在 #what 中使用#c_str(),则错误输出始终为空
- 在抛出 what() 的实例后调用'std::logic_error'终止:basic_string::_M_construct 空无效
- "what does ":*** [可执行文件] 错误 1 " mean ?"
- 为什么玩家控制器"own"偏航俯仰和滚动,但角色"owns"它的位置?
- 这在C++ "It does not own the underlying data, and so is cheap to copy or assign"中意味着什么
- What is the std::chrono::time_point equivalent of std::numer
- WIC 工厂将始终在 Windows7 上为 nullptr("What's a Creel?"教程中使用)
- 自定义派生的 std::exception 类的 'what' 函数返回神秘的废话
- What is std::vector::_emplace_back_slow_path / std::vector::
- 在抛出 'std::invalid_argument' 的实例后终止调用 what(): stoi (找不到问题所在)
- "terminate called after throwing an instance of std::invalid_argument' what(): stoi ?"
- Does std::exception own `what()`?