是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?

或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?


当前回答

拥有多个出口点本质上与使用GOTO是一样的。这是不是件坏事取决于你对迅猛龙的看法。

其他回答

我倾向于单一退出,除非事情真的变得复杂。我发现在某些情况下,多个存在点可以掩盖其他更重要的设计问题:

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

在看到这段代码时,我马上会问:

'foo'是否为空? 如果是这样,有多少客户端'DoStuff'曾经调用一个空'foo'函数?

根据这些问题的答案,可能是这样

这种检查毫无意义,因为它从来都不是真的。它应该是一个断言) 这种检查很少是正确的,所以最好改变那些特定的调用函数,因为它们可能应该采取一些其他的操作。

在上述两种情况下,代码可能都可以用断言重做,以确保'foo'永远不为空,并更改相关的调用者。

还有另外两个原因(我认为是针对c++代码的),多重存在实际上会产生负面影响。它们是代码大小和编译器优化。

在函数出口作用域中的非pod c++对象将调用其析构函数。如果有几个return语句,那么作用域中可能有不同的对象,因此要调用的析构函数列表也会不同。因此,编译器需要为每个return语句生成代码:

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

如果代码大小是一个问题,那么这可能是值得避免的。

另一个问题涉及到“命名返回值优化”(又名复制省略,ISO c++ '03 12.8/15)。c++允许实现在可以的情况下跳过调用复制构造函数:

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

就像代码一样,对象'a1'是在'foo'中构造的,然后它的复制构造将被调用来构造'a2'。然而,复制省略允许编译器在堆栈上与'a2'相同的位置构造'a1'。因此,当函数返回时,不需要“复制”对象。

多个出口点会使编译器的检测工作复杂化,至少对于相对较新的vc++版本,优化不会发生在函数体有多个返回的地方。有关详细信息,请参阅Visual c++ 2005中的命名返回值优化。

我倾向于认为函数中间的return语句是不好的。您可以使用返回在函数的顶部构建一些保护子句,当然也可以告诉编译器在函数的末尾返回什么,但函数中间的返回很容易被忽略,并且会使函数更难解释。

我曾经使用过糟糕的编码标准,强迫你使用单一的退出路径,如果函数不是琐碎的,结果几乎总是非结构化的意大利面条——你最终会遇到许多中断和继续。

作为嵌套if的替代方法,有一种使用do/while(false)在任何地方爆发的方法:

    function()
    {
        HRESULT error = S_OK;

        do
        {
            if(!SUCCEEDED(Operation1()))
            {
                error = OPERATION1FAILED;
                break;
            }

            if(!SUCCEEDED(Operation2()))
            {
                error = OPERATION2FAILED;
                break;
            }

            if(!SUCCEEDED(Operation3()))
            {
                error = OPERATION3FAILED;
                break;
            }
            if(!SUCCEEDED(Operation4()))
            {
                error = OPERATION4FAILED;
                break;
            }
        } while (false);

        return error;
    }

这为您提供了一个出口点,允许您进行其他嵌套操作,但仍然不是真正的深层结构。如果你不喜欢成功,你可以做任何失败的事情。这类事情还允许您在任意两个其他检查之间添加其他代码,而无需重新缩进任何内容。

如果你真的疯了,整个If块也可以宏化。: D

    #define BREAKIFFAILED(x,y) if (!SUCCEEDED((x))) { error = (Y); break; }

    do
    {
        BREAKIFFAILED(Operation1(), OPERATION1FAILED)
        BREAKIFFAILED(Operation2(), OPERATION2FAILED)
        BREAKIFFAILED(Operation3(), OPERATION3FAILED)
        BREAKIFFAILED(Operation4(), OPERATION4FAILED)
    } while (false);

我使用多个出口点使错误情况+处理+返回值尽可能接近。

所以必须测试条件a, b, c必须为真,你需要用不同的方式处理它们:

if (a is false) {
    handle this situation (eg. report, log, message, etc.)
    return some-err-code
}
if (b is false) {
    handle this situation
    return other-err-code
}
if (c is false) {
    handle this situation
    return yet-another-err-code
}

perform any action assured that a, b and c are ok.

a, b和c可能是不同的东西,比如a是输入参数检查,b是新分配内存的指针检查,c是检查参数a中的值。