【问题标题】:Best practices for using `std::error_code`使用 `std::error_code` 的最佳实践
【发布时间】:2020-10-18 01:37:01
【问题描述】:

我目前正在构建嵌入式系统并使用现代 C++ 编译器。 虽然我在技术上可以在给定资源(ARM7,超过 10M RAM)中进行异常处理,但我认为异常不是处理此类事情的正确工具,并且使用异常需要 RTTI,这反过来会导致代码膨胀。

为了保持 C++ 风格,我想使用 std::error_code(或类似的更多数据),因为我确实喜欢这个概念。

但是,对于如何实际使用它们似乎没有任何共识。我见过至少四种在函数调用之间传递它们的不同方式,其中两种具有多种语义。

  1. 通过指针作为参数传递

    void somefunction(Args..., std::error_code* error);
    

    这是我不经常看到的方式,也是我最不喜欢的方式。它使返回类型完全可用,并且(通常但不总是)传递 nullptr 导致正常的抛出行为。

  2. 通过引用作为参数传递

    void somefunction(Args..., std::error_code& error);
    

    这是我更喜欢的一个。它使returnvalue 完全可用,并明确指出error_code 不是可选的。

  3. 按值返回

    std::error_code somefunction(Ret& out <= if used, Args...);
    

    我经常看到这个,但不是很喜欢它,因为它会耗尽你的返回值,而且我通常不喜欢“输出参数”,除非没有办法绕过它们。

  4. 返回std::variant&lt;Ret, std::error_code&gt;

    std::variant<Ret, std::error_code> somefunction(Args...);
    

    这允许返回值,但使访问值和错误更加困难。此外,它使调用函数的代码更加冗长。

语义

如果 error_code 被传递,我已经看到了语义不同的方式 1 和 2。

  • 启动时清除并设置错误
  • 仅在出错时设置
  • 如果error_code 为“设置”,则在开始时右转

如果您想减少调用代码中的错误检查,最后一种方法非常好。因为您可以将一个 error_code 传递给多个函数,而无需在两者之间进行检查,并且第一个错误之后的所有内容都不会执行,类似于异常的执行方式。

我个人更喜欢方式2的检查和返回,但是我可能有偏见。

有一些推荐/普遍接受的方法吗?

【问题讨论】:

  • 你好。也许有点离题,但我会使用异常。您可以“自定义”它们传输您自己的错误代码,对函数原型没有“影响”,并且您可以肯定,如果有人忘记检查“返回”的错误代码,代码将崩溃(在开发阶段)。调用者代码也更干净,您可以在快乐路径(无错误)和异常处理路径之间拆分,而无需在每个函数调用位置测试返回的错误代码
  • 问题是我可能必须将部分或全部应用程序移植到低规格平台,在这种情况下可能没有足够的空间来容纳异常版本(这会为异常添加大量代码处理和 rtti)。
  • 我认为指针是这个例子中的方法,通过引用也很好,但这也只是我的偏见,我认为没有对错。
  • 为什么异常处理需要RTTI?我认为这些是独立的属性。
  • @MarkRansom 因为你可能会抛出 std::runtime_error 并抓住 const std::exception&amp;。编译器(或者确切地说是异常实现)需要一种方法来确定它们是否匹配,并且它们使用 rtti 这样做(类似于 dynamic_cast 所做的)。您可以禁用 rtti 并使用异常,但这只会禁用前端,但仍会发出实际的 rtti 信息和后端函数。

标签: c++ error-code


【解决方案1】:

好的,这不是完整的答案,实际上也不是完美的主题,因为我不知道执行此操作的标准方法。但我曾经看到一个漂亮的小技巧,可以让错误代码更难被误用。考虑以下代码:

结构 MyEC {
    我的EC(){}
    我的EC(我的EC &&其他):父母(&其他){
        // 如果未检查其他,则可能记录和或中止
        其他.checked = false;
    }
    // 删除其他构造函数和赋值运算符

    〜我的EC(){
        if(!checked && parent == nullptr) {
            // 记录和或中止
        }
     }

    [[nodiscard]] std::error_code check() {
        检查=真;
        返回 ec;
     }
 
     无效集(std::error_code err){
         如果(父 == nullptr) ec = err;
         否则父母->设置(错误);
     }
私人的:
    MyEC* 父级 = nullptr;
    检查=真;
    std::error_code ec {};
};

int foo(MyEC&& err) {
    err.set(/* 一些错误 */);
    返回 5;
}

int foo1(MyEC&&) {
    返回 4;
}

无效酒吧(){
    MyEC 错误;
    富(标准::移动(错误));
    // err 有错误代码,如果没有检查,我们会知道
 

    foo1(std::move(err));
    // 即使没有发生错误,如果没有检查错误,我们也会中止。
}

当错误代码未设置但也未检查时,它甚至会中止,这非常好。移动后有很多用处,有点奇怪,不过这里没问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    • 2012-12-31
    • 1970-01-01
    • 2017-11-29
    • 2010-10-08
    • 2012-04-11
    相关资源
    最近更新 更多