我使用从std::system_error
继承的类进行错误处理,我想控制调用what()
时返回的内容。原因:标准(C ++ 11和草案C ++ 1y CD - N3690,下面的§引用都是后者)没有说明what()
返回的字符串究竟是什么样子,它只是给出§19.5.6.2(14)中的注释:
注意:返回的NTBS可能是
what_arg + ": " + code.message()
的内容。 - 结束说明
所以它应被视为依赖于实现。 (顺便说一句,不应该是code().message()
而不是code.message()
?)
所以,问题是:如果我想要符合标准并且不依赖于实现(即想要可移植),我如何准确定义what()
返回的字符串?
对于那些喜欢代码的人:
class my_class : public std::system_error {
public:
my_class(std::error_code ec, std::string const & what_arg)
: system_error(ec, /* this string is not required to be equal to what is returned by what() */)
{
// ok, try it here
// but what is the name of the member storing the string?
}
const char * what() const noexcept
{
// ok, try it here
// but how to access what_arg in its unaltered form?
}
};
好的,我不喜欢的一个简单的解决方案可能如下:
class my_class : public std::system_error {
std::string my_what;
public:
my_class(std::error_code ec, std::string const & what_arg)
: system_error(ec, what_arg),
my_what( /* construct my what string */ )
{ }
const char * what() const noexcept
{ return my_what.c_str(); }
};
由于std::exception::what()
是虚拟的,它会起作用,但是有没有使用任何实现细节的更优雅的方式?我不喜欢存储两个字符串的想法:一个存储在std::system_error
中,另一个存储在my_what
中。
问题的根源:std :: runtime_error - 碰巧是std :: system_error的父类 - 在§1.9.2.6(3)中有一个确切的要求,这是构造函数的后置条件:
strcmp(what(), what_arg.c_str()) == 0
在std::system_error
的情况下,§19.5.6.2(2)中出现以下内容:
string(what()).find(what_arg) != string::npos
是否有人知道为什么标准会如此努力地将code().message()
纳入what()
?请注意code()
返回错误代码对象,因此任何人都可以随时在字符串中包含code().message()
(即使在捕获此类的异常时)。
如果std::system_error
的要求与std::runtime_error
的要求相同,我可以写一下:
class my_class : public std::system_error {
public:
my_class(std::error_code ec, std::string const & what_arg)
: system_error(ec, /* build my what string here */ )
{ }
};
有没有优雅便携的解决方案?
更新:
下面的大量注释表明错误消息是实现定义的。我理解,我只想格式化 what()
返回的字符串,我不想在所有系统上逐字节等效。考虑一下我想记录它或将它传递给第三方,它应遵循一些固定的格式(这不是标准建议的)。
UPDATE2: 我相信std :: system_error不只是OS或STL错误。我可以(并且假设)从中派生自己的类并将它们用于错误报告。如果我正在编写低级API怎么办?顺便说一句,为什么禁止在高级API中使用它?
如果我在API的错误处理部分中将所有参数传递给它的构造函数,则会涉及无实现定义(即未知)错误字符串,但我仍然无法复制它而不重复数据
答案 0 :(得分:6)
我不知道“优雅和便携”的解决方案,但我认为您在问题中提出的解决方案没有任何问题,即制作您自己的what_arg
副本。
异常对象不会持续很长时间:通常只有足够长的时间来展开堆栈。此外,what_arg
中的字符串可能不会很长,因为一个兆字节的what
不会非常易读。最后,异常对象只会在特殊情况下创建,因此小字符串的“不必要”重复不会对程序的性能产生任何明显的影响。
基本上,类设计者选择使类的状态不透明,而且 - 直言不讳 - 你不相信它们从该状态产生可用的结果(可读的消息)。在这种情况下,您将不得不复制州。这是状态封装的一种并不罕见的结果,一般的解决方案几乎总是复制状态。在这种情况下,成本很小,因此如果控制what()
的价值对您来说很重要,那么您应该接受成本并继续前进。
答案 1 :(得分:2)
我想发表更长时间的评论,所以我会将其发布为社区维基回答。
标准(C ++ 14草案N3690)描述了[syserr.syserr.overview] / 1中的system_error
:
类
system_error
描述了一个异常对象,用于报告具有关联错误代码的错误情况。此类错误情况通常源自操作系统或其他低级应用程序接口。
来自操作系统或其他低级API 的错误和错误消息必然是非便携式。
此外,如果您派生自system_error
,那么通过ref捕获异常的人可能会期望来自C ++实现所指定的OS /低级API的错误代码;至少此用户知道标准不保证what()
的结果。如果此用户捕获派生类型的异常,您还可以提供另一个精确返回字符串的what2()
函数。
我建议的解决方案 来源于system_error
。据我所知,您对保证的分析(以及拼写错误code().message()
)是正确的,即您无法准确指定what()
对system_error
的回报。
如果您想在异常中使用错误代码,可以使用error_code
工具;然而,与system_error
一样,标准在[syserr] / 1
本小节描述了标准库和C ++程序可用于报告源自操作系统或其他低级应用程序接口的错误条件的组件。
因此,最好使用其他错误代码类型。
据我所知,如果要从源自OS /低级API的system_error
增强错误消息,则从system_error
派生非常有用。在这种情况下,您可能希望在消息中添加一些描述(what()
),这就是传递给ctor的消息的system_error
保证。
另一个“评论”,回答您如何从system_error
派生并精确指定what()
的回报:
class my_class : public std::system_error
{
std::string my_what;
public:
my_class(std::error_code ec, std::string const& what_arg)
: system_error(ec)
// ^^^^ note: NOT passing the string
, my_what( /* construct my what string */ )
{ }
const char* what() const noexcept
{ return my_what.c_str(); }
};
是的,在某种意义上存在数据重复,即(可能)有两个字符串对象,但是字符串内容没有数据重复。 如果您不需要将字符串传递给异常的ctor(例如异常的类型和错误代码足够描述),那么您甚至可以省略字符串对象;
之类的东西class my_class : public std::system_error
{
public:
my_class(std::error_code ec)
: system_error(ec)
// ^^^^ note: NOT passing the string
{ }
const char* what() const noexcept
{ return "description of error"; }
};
虽然最好在what()
返回的说明中包含错误代码(需要my_class
中的某种存储空间)。
值得一提的是,构建字符串 on demand (即调用what()
时)可能是有利的。