我正在从MyException
派生我自己的异常,称之为std::system_error
,并已覆盖what()
来计算并返回我的消息。 MyException
的初始化列表不会调用收到消息的system_error构造函数覆盖。
如果我抓住MyException
并将其复制到std::exception
,则what()
上std::exception
的调用结果为nullptr
。这是有道理的。
我的问题是,如果我确实使用了在初始化MyException
时收到消息的system_exception构造函数,是否指定system_error将获取消息的副本并拥有它并释放它?
我假设这会使std::exception
MyException
副本能够返回有效的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;
}
答案 0 :(得分:6)
我的问题是,如果我确实使用了在初始化
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:
派生自类
T
的每个标准库类exception
都应具有可公开访问的复制构造函数和不能以异常退出的可公开访问的副本赋值运算符。这些成员函数应满足以下后置条件:如果两个对象lhs
和rhs
都具有动态类型T
而lhs
是rhs
的副本,则{ {1}}等于strcmp(lhs.what(), rhs.what())
。
因此,必须有一个复制构造函数,它必须永远不会抛出,并且副本必须保持0
的相同返回值。同样,对于复制赋值运算符。
综上所述,我们可以推测what()
必须在引用计数字符串内部保留您为std::runtime_error
传递的值(以避免复制时分配的例外),并且值将保持不变无论是复制和/或切片 - 还是只有what_arg
, 不 到std::runtime_error
! (关于std::exception
存储的基本原理和要求的更多信息可以在@HowardHinnant这个非常有趣的答案中找到:move constructor for std::runtime_error)
what
继承自std::system_error
,所以同样适用于它和从中派生的任何类型(只要派生类型保持非抛出复制构造函数不变)。
我假设这会使
std::runtime_error
std::exception
副本能够返回有效的MyException
。
没有!当您创建what()
的{{1}} 副本时,您将slicing对象缩减为比std::exception
的值更少的派生类型物理存储。如果必须制作异常的副本,则可以使用的派生类型最少为MyException
。 (当然,您可以随时安全地将what
引用发送到std::runtime_error
。)换句话说,永远可能从std::exception
对象的MyException
中获取有意义的字符串。
此代码具有您想要的行为,便携式:
std::exception
我会说编写一个分配的异常构造函数(由于显而易见的原因)是不好的形式,但考虑到我所知道的每个当前标准库实现对what()
使用short-string optimization,这在实践中极不可能成为一个问题。
答案 1 :(得分:1)
您的问题与理解异常的生命周期有关。该问题在帖子here和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上使用的异常指针的例子,但是我使用了你的异常类而不是像std::logic_error
那样的标准异常
至于你原来的问题,似乎难以作出艰难的保证。我在异常赋值运算符的文档中找到了以下语句,适用于C ++ 11。在C ++ 98中,甚至没有提供此保证。
C ++标准库中的每个异常(包括此内容)至少都有一个复制赋值运算符重载,它保留成员返回的字符串表示形式,当动态类型匹配时。
但是,std::system_error
的动态类型与您案例中std::exception
的动态类型不匹配,因此我认为不会保证其有效。
答案 2 :(得分:0)
类异常不拥有任何字符串。当您对异常对象进行切片时,您将获得一个基本异常对象,该对象没有覆盖what()虚函数。
what()函数的神奇之处在于虚函数what()和派生类中的函数。您可以将存储在静态内存中的const char *传递给异常对象,并且不会复制它。
请注意,引发和异常时对象的副本可能会引发新的异常并且不会推荐它(例如,在bad_alloc之后,您可能无法创建新的字符串对象)。这就是为什么通过引用而不是通过值来更好地捕获异常的原因。