C++核心准则E.27:如果无法抛出异常,系统化运用错误处理代码

时间:2022-07-23
本文章向大家介绍C++核心准则E.27:如果无法抛出异常,系统化运用错误处理代码,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

E.27: If you can't throw exceptions, use error codes systematically

E.27:如果无法抛出异常,系统化运用错误处理代码

Reason(原因)

Systematic use of any error-handling strategy minimizes the chance of forgetting to handle an error.

系统化运用任何错误处理策略都可以减少忘记处理某个错误的可能性。

See also: Simulating RAII

参见:模仿RAII方式进行资源管理

Note(注意)

There are several issues to be addressed:

有多个课题需要说明:

  • How do you transmit an error indicator from out of a function?
  • 如何在函数外部发送错误指示器?
  • How do you release all resources from a function before doing an error exit?
  • 如何在执行发生错误从函数退出之前释放所有资源?
  • What do you use as an error indicator?
  • 使用什么作为错误指示器?

In general, returning an error indicator implies returning two values: The result and an error indicator. The error indicator can be part of the object, e.g. an object can have a valid() indicator or a pair of values can be returned.

一般情况下,返回错误指示器包含包含两个值:结果和错误指示器。错误指示器可以是对象的一部分,例如对象可以包含一个valid()检查函数或者一对可以返回的值。

Example(示例)

Gadget make_gadget(int n)
{
    // ...
}

void user()
{
    Gadget g = make_gadget(17);
    if (!g.valid()) {
            // error handling
    }
    // ...
}

This approach fits with simulated RAII resource management.

这个做法符合模拟RAII的资源管理。

The valid() function could return an error_indicator (e.g. a member of an error_indicator enumeration).

valid()函数可以返回error_indicator(例如,枚举类型error_indicator的某个成员。

Example(示例)

What if we cannot or do not want to modify the Gadget type? In that case, we must return a pair of values. For example:

如果我们不能或不想修改Gadget类型时该怎么办呢?这种情况,我们必须返回值对。例如:

std::pair<Gadget, error_indicator> make_gadget(int n)
{
    // ...
}

void user()
{
    auto r = make_gadget(17);
    if (!r.second) {
            // error handling
    }
    Gadget& g = r.first;
    // ...
}

As shown, std::pair is a possible return type. Some people prefer a specific type. For example:

如代码所示,std::pair是可能的返回值类型。有些人更愿意使用特殊类型。例如:

Gval make_gadget(int n)
{
    // ...
}

void user()
{
    auto r = make_gadget(17);
    if (!r.err) {
            // error handling
    }
    Gadget& g = r.val;
    // ...
}

One reason to prefer a specific return type is to have names for its members, rather than the somewhat cryptic first and second and to avoid confusion with other uses of std::pair.

更愿意使用特殊返回值类型的一个原因是可以为成员命名,而不是有些难以理解的first和second,另外的好处就是可以和使用std::pair的其他代码混淆。

Example(示例)

In general, you must clean up before an error exit. This can be messy:

一般情况下,你必须在错误退出之前执行清理动作。这可能产生凌乱的代码:

std::pair<int, error_indicator> user()
{
    Gadget g1 = make_gadget(17);
    if (!g1.valid()) {
        return {0, g1_error};
    }

    Gadget g2 = make_gadget(31);
    if (!g2.valid()) {
        cleanup(g1);
        return {0, g2_error};
    }

    // ...

    if (all_foobar(g1, g2)) {
        cleanup(g2);
        cleanup(g1);
        return {0, foobar_error};
    }

    // ...

    cleanup(g2);
    cleanup(g1);
    return {res, 0};
}

Simulating RAII can be non-trivial, especially in functions with multiple resources and multiple possible errors. A not uncommon technique is to gather cleanup at the end of the function to avoid repetition (note the extra scope around g2 is undesirable but necessary to make the goto version compile):

模拟RAII可能需要特别处理,特别是包含多个资源和多个错误的时候。一个并不罕见的技术是将清除动作集中在函数末尾以避免重复(注意包含g2的额外作用域本来是不需要的,只是为了让goto版本代码通过编译)

std::pair<int, error_indicator> user()
{
    error_indicator err = 0;
    int res = 0;

    Gadget g1 = make_gadget(17);
    if (!g1.valid()) {
        err = g1_error;
        goto g1_exit;
    }

    {
        Gadget g2 = make_gadget(31);
        if (!g2.valid()) {
            err = g2_error;
            goto g2_exit;
        }

        if (all_foobar(g1, g2)) {
            err = foobar_error;
            goto g2_exit;
        }

        // ...

    g2_exit:
        if (g2.valid()) cleanup(g2);
    }

g1_exit:
    if (g1.valid()) cleanup(g1);
    return {res, err};
}

The larger the function, the more tempting this technique becomes. finally can ease the pain a bit. Also, the larger the program becomes the harder it is to apply an error-indicator-based error-handling strategy systematically.

函数越大,使用这类技术的诱惑越大。finally可以稍微减轻痛苦。同时,问题越大,基于错误指示器的系统化错误处理策略就越难运用。

We prefer exception-based error handling and recommend keeping functions short.

我们比较喜欢基于异常的错误处理并且推荐保持函数短小。

See also: Discussion

参见:问题讨论

See also: Returning multiple values

参见:如果需要返回多个输出值,最好返回结构体或者tuple

Enforcement(实施建议)

Awkward.

不容易。

原文链接

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#e26-if-you-cant-throw-exceptions-consider-failing-fast