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 将获取消息的副本并且拥有并释放它?
我假设这将使 MyException
的 std::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
不拥有 what
– std::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_arg
的 copy,因为对传入值的生命周期没有要求。
接下来是[异常]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<>
,这在实践中极不可能成为问题。
我从 std::system_error
导出我自己的异常,将其称为 MyException
并覆盖 what()
来计算和 return 我的消息。 MyException
的初始化列表不调用接收消息的 system_error 构造函数覆盖。
如果我捕获 MyException
并将其复制到 std::exception
,则在 std::exception
上调用 what()
的结果是 nullptr
。这是有道理的。
我的问题是,如果我确实使用 system_exception 的构造函数在初始化 MyException
时获取消息,是否指定 system_error 将获取消息的副本并且拥有并释放它?
我假设这将使 MyException
的 std::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
不拥有 what
– std::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_arg
的 copy,因为对传入值的生命周期没有要求。
接下来是[异常]p2:
Each standard library class
T
that derives from classexception
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 objectslhs
andrhs
both have dynamic typeT
andlhs
is a copy ofrhs
, thenstrcmp(lhs.what(), rhs.what())
shall equal0
.
因此,必须有一个复制构造函数,它绝不能抛出,并且副本必须为 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 ofMyException
to be able to return a validwhat()
.
没有!当您对 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<>
,这在实践中极不可能成为问题。