优雅的方式在没有goto的情况下在C ++中执行多个依赖错误检查,而且没有提前返回?

时间:2014-07-18 20:08:42

标签: c++ error-handling goto

假设我想连续调用四个函数,这些函数在我的某个对象上运行。如果其中任何一个失败,我想在不调用其他人的情况下返回FAILURE,如果所有这些都成功完成,我想返回SUCCESS

通常情况下,我会这样做:

if(function_zero(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}
if(function_one(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}
if(function_two(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}
if(function_three(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}

return SUCCESS;

或者,如果我需要做一些清理:

if(function_zero(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}
if(function_one(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}
if(function_two(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}
if(function_three(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}

cleanup:
// necessary cleanup here
return status;

然而,我正在进行的项目有一些限制:

  • 没有goto,永远
  • 没有提前退货(每个功能一次退货)
  • 行长限制
  • (编辑)没有例外。
  • (编辑)没有模板。

这导致我这样的事情:

if(function_zero(&myMutableObject) == SUCCESS)
{
    if(function_one(&myMutableObject) == SUCCESS)
    {
        if(function_two(&myMutableObject) == SUCCESS)
        {
            status = function_three(&myMutableObject);
        }
        else
        {
            status = FAILURE;
        }
    }
    else
    {
        status = FAILURE;
    }
}
else
{
    status = FAILURE;
}

return status;

不幸的是,这经常使我违背行长限制。

我的问题是:有没有更简单的方式来写这个?

备注&限制:

  • 我必须在这里隐含的代码块中实现这个逻辑。我无法创建新功能,重构或更改整体架构。
  • (编辑)实际上,这些功能的签名截然不同。

7 个答案:

答案 0 :(得分:7)

使用例外和RAII。这些都是他们发明要解决的问题。例外情况更多的是系统范围的功能,而不是您可以在本地应用的功能。

对于清理块,RAII正是您需要的功能。

对于成功/失败,我们可以使用lambdas和variadics隐式链接它们。

现在我们可以简单地将它们写成列表中的lambda。

status f() {
    struct nested {
        static template<typename F> status_t Check(F f) {
            return f();
        }
        static template<typename F, typename... Chain> status_t Check(F f, Chain... chain) {
            auto status = f();
            return status != failure ? Check(chain...) : status;
        }
    };
    return nested::Check( 
        [] { return function_zero(&myMutableObject); },
        [] { return function_one(&myMutableObject); },
        [] { return function_two(&myMutableObject); },
        [] { return function_three(&myMutableObject); },
    );
}

如果您需要捕获返回值,这会变得稍微更多问题,但由于它似乎总是带有out参数的错误代码,如果您只是声明接收变量,那么应该没问题f(),然后所有未来的lambdas都可以参考它。它也不要求每个函数都具有相同的签名,或者分配各种数据结构。

答案 1 :(得分:5)

如果状态当前设置为SUCCESS,则仅使用&amp;&amp;运营商。如果设置为FAILURE&amp;&amp;将立即失败,后续测试将不会执行。

status = SUCCESS

if (status == SUCCESS && function_zero(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
if (status == SUCCESS && function_one(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
if (status == SUCCESS && function_two(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
if (status == SUCCESS && function_three(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}

return status;

正如@Mooing Duck建议的那样,你可以简单地在其他链中做到这一切:

status = SUCCESS

if (function_zero(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
else if (function_one(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
else if (function_two(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
else if (function_three(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}

return status;

答案 2 :(得分:3)

我在处理长链C调用(通常是WinAPI)时多次使用的模式如下:

bool ret =
    function_zero(&myMutableObject) == SUCCESS
    &&
    function_one(&myMutableObject) == SUCCESS
    &&
    function_two(&myMutableObject) == SUCCESS
    &&
    function_three(&myMutableObject) == SUCCESS;
if(!ret)
{
    // cleanup
}
return ret?SUCCESS:FAILURE;

您甚至可以将&&留在每行的末尾,这样它看起来更像是&#34;正常&#34;电话顺序(虽然我个人更喜欢这种方式,但它更清楚的是什么)。

&&运算符保证以正确的顺序执行,并且只有先前的调用成功,并在调用之间引入必要的序列点(或者在C ++ 11中调用它们),所以顺序很好地定义了各种调用之间的参数评估。此外,它具有足够低的优先级,不需要额外的括号。

如果你不害怕使用COM风格的宏,你也可以将== SUCCESS检查封装在一个宏中,比如

// in some header, maybe with a less abused name
#define OK(x) ((x) == SUCCESS)

bool ret =
    OK(function_zero(&myMutableObject))
    &&
    OK(function_one(&myMutableObject))
    &&
    OK(function_two(&myMutableObject))
    &&
    OK(function_three(&myMutableObject));
// ...

更好的是,如果SUCCESS != 0FAILURE == 0您完全放弃OK()== SUCCESS,只需使用&&来关联来电。

答案 3 :(得分:2)

你说没有例外,但我认为你应该知道你放弃了什么。

如果您使用基于RAII的清理并将错误报告为异常而不是代码,那么您将获得如下代码:

function_zero(&myMutableObject);
function_one(&myMutableObject);
function_two(&myMutableObject);
function_three(&myMutableObject);

这是一个解释正确的C ++异常处理及其好处的网站:

http://exceptionsafecode.com/

一些好处是:

  • 更容易阅读
    • 更易于理解和维护
    • 错误的代码看起来不对
  • 更容易写
  • 改善了成功路径的表现
    • &#39;零成本&#39;例外
    • 编译器将异常理解为语言特性,知道哪条路径是成功路径,哪条路径是故障路径
    • 异常表的代码大小增加通过消除错误检查代码
    • 而抵消

此外,赫伯·萨特(Herb Sutter)就单一退出问题提出了这个问题。 (你的常用名称&#34;没有提前退货&#34;规则):

  

总的来说,请注意SE / SE是一个过时的想法,而且一直都是   错误。 “单一条目”,或者功能应该始终如一的想法   进入一个地方(在他们的开始),而不是从goto跳跃   调用者的代码直接到函数体内的随机位置,是   并且是计算机科学中非常有价值的进步。它是什么   使图书馆成为可能,因为它意味着你可以打包一个   功能和重用它,功能总是知道它的起始   状态,它开始的地方,无论调用代码如何。 “单出口,”   另一方面,在优化的基础上得到了不公平的欢迎   ('如果有一次返回,编译器可以执行返回值   优化更好' - 见上面反例)和对称性('如果   单一进入是好的,单一退出也必须是好的')但这是错误的   因为原因不能反过来允许呼叫者跳入   是不好的,因为它不受功能的控制,但允许   当它知道它已经完成时,它会自动返回   很好,完全在功能的控制下。

http://herbsutter.com/category/c/gotw/page/4/

您应该尝试更新规则,即使这些规则只适用于新项目。

答案 4 :(得分:1)

您可以将函数指针存储在std :: function的容器中(只要它们具有与示例中相同的签名):

std::vector<std::function<error_code (myobject&)> functions { func1, func2, func3, func4 };

error_code status = SUCCESS;
for (const auto& f : functions) {
    if (f(myobject) == ERROR) {
        clean_up();
        status = ERROR;
        break;
    }
}

return status;

答案 5 :(得分:1)

这可行:

bool success = true;
success = success && function_zero(&myMutableObject) != FAILURE;
success = success && function_one(&myMutableObject) != FAILURE;
success = success && function_two(&myMutableObject) != FAILURE;
success = success && function_three(&myMutableObject) != FAILURE;

return success ? SUCCESS : FAILURE;

返回可以替换为:

int status = SUCCESS;
if( !success ) status = FAILURE;
return status;

如果贵公司也禁止使用条件操作员。

答案 6 :(得分:0)

您可以编写一个简单的本地仿函数:

bool fn0(int&) { return false; }
bool fn1(int&) { return false; }
bool fn2(int&) { return false; }

int main() {
    struct Test {
        const bool result;

        typedef bool (*function)(int&);
        Test(function f, int& value)
        :   result(f(value))
        {};

        operator bool () const { return result; }
    };

    int value;
    return Test(fn0, value)
        && Test(fn1, value)
        && Test(fn2, value);

}