8

我使用继承自的类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_errormy_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 不仅适用于操作系统或 STL 错误。我可以(并且假设)从中派生出我自己的类并将它们用于错误报告。如果我正在编写低级 API 怎么办?顺便问一下,为什么禁止在高级 API 中使用它?

如果我在我的 API 的错误处理部分将所有参数传递给它的构造函数,则不涉及实现定义(即未知)的错误字符串,但我仍然无法在不复制数据的情况下对其进行格式化。

4

2 回答 2

6

我不知道“优雅且便携”的解决方案,但我认为您在问题中提出的解决方案没有任何问题,即制作自己的what_arg.

异常对象不会持续很长时间:通常,仅足以展开堆栈。此外,字符串 inwhat_arg不太可能很长,因为一兆字节的what可读性不强。最后,异常对象只会在异常情况下创建,因此“不必要”的小字符串重复不会对程序性能产生任何明显影响。

基本上,类设计者选择使类的状态不透明,并且——坦率地说——你不相信他们会从该状态产生可用的结果(可读消息)。在这种情况下,您将不得不复制该状态。这是状态封装的常见结果,一般的解决方案几乎总是复制状态。在这种情况下,成本很小,所以如果控制价值对what()你很重要,你应该接受成本并继续前进。

于 2013-07-04T07:14:27.427 回答
2

我想发表更长的评论,因此我将其发布为社区 wiki 答案。

标准(C++14 草案 N3690)system_error在 [syserr.syserr.overview]/1 中描述:

该类system_error描述了一个异常对象,用于报告具有相关错误代码的错误条件。这种错误条件通常源自操作系统或其他低级应用程序接口。

源自操作系统或其他低级 API的错误和错误消息必然是不可移植的

此外,如果您从 派生system_error,则通过 ref 捕获异常的人可能会期望来自 C++ 实现指定的操作系统/低级 API 的错误代码;至少该用户知道what()标准不保证结果。如果此用户捕获了您的派生类型的异常,您不妨提供另一个what2()函数来精确返回您的字符串。

我建议的解决方案不是system_error. 据我所知,您对保证(和错字code().message())的分析是正确的,即您无法准确指定what()for的返回system_error。如果你想在你的异常中有一个错误代码,你可以使用这些error_code工具;然而,与 一样system_error,标准在 [syserr]/1 中指定

本小节描述标准库和 C++ 程序可用于报告源自操作系统或其他低级应用程序接口的错误情况的组件。

因此,使用其他错误代码类型可能会更好。

据我所见,如果您想增强源自操作系统/低级 API 的 ssystem_error的错误消息,则派生自很有用。system_error在这种情况下,您可能希望向消息 ( what()) 添加一些描述,这就是system_error传递给 ctor 的消息的保证。


另一个“评论”来回答您如何从 得出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)。

值得一提的是,按需构建字符串(即何时what()调用)可能是有利的。

于 2013-07-04T16:18:00.977 回答