9

我在某处阅读了一条规则:

遵循单进单出规则。永远不要在同一个函数中编写多个返回语句。

这个说法是真的吗?如果是这样,您能否详细说明我们为什么要遵循这条规则?

4

6 回答 6

24

这是真的?

这就是规则所说的,在它被使用和执行的地方。这是一个好规则吗?我反对它的采用牙齿和指甲。我认为这是一个愚蠢的规则。比愚蠢更糟糕:这是对 C++ 有害的规则。

我同意规则的第一部分,“单次入场”。Fortranentry语句导致的问题比它解决的问题多得多。规则的第一部分不适用于 C 或 C++,原因很简单,两种语言都没有提供多入口点机制。“单项”是 C 和 C++ 中的无操作。

那么“单出口”呢?提前返回不一定会造成问题。在返回之前未能处理分配的资源是导致问题的原因。正确的规则是“清理你的烂摊子”,或者不要留下悬空的资源。单次退出并不能解决这个问题,因为它并没有说明清理你的烂摊子。

在 C 中,单入口/单出口规则通常与允许(甚至鼓励)使用goto错误处理密切相关。我可以看到goto在 Linux 内核代码中用于错误处理的地方。但不是在 C++ 中。这就是为什么我写了单入口/单出口在 C++ 中是有害的。此规则不鼓励使用 RAII 和异常安全编程,并鼓励使用goto.

于 2012-10-05T12:20:53.017 回答
12

不,这不是规则,有时甚至很难/不可能实现。但是,具有一个入口和一个出口点的代码更易于理解和调试。比较一下:

int foo()
{
    if(something)
        return 0;
    //100 lines of code
    if(something)
        return 11;
    //100 lines of code
    if(something)
        return -1;
    //100 lines of code
    return 0;
}

还有这个:

int foo()
{
    int errorCode = 0;
    if(something)
        errorCode = 1;
    //100 lines of code
    if(something)
        errorCode = 11;
    //100 lines of code
    if(something)
        errorCode = -1;
    //100 lines of code
    return errorCode;
}

现在我们只有一个退出点,并且(也考虑到变量名)更容易理解函数的作用。您还可以在最后一个返回上放置一个断点,并知道这是函数结束的点,并且您肯定会命中它。

于 2012-10-05T11:39:13.480 回答
6

此规则可能适用于 C,但由于异常,它在 C++ 中可能被认为已过时。一旦你的函数抛出一个异常或调用一个可以抛出的函数,你就会有一个额外的退出点:

int f()
{
  //...
  g(); // g() may throw: you have an exit point here
  //...
  throw exc; // another possible exit point
  //...
  return returnValue; // Nice try, but you have additional exit points
}

除了其他答案中的要点之外:此规则旨在使代码更易于遵循,但很容易找到不正确的示例。好多了:

if (condition)
  return a;
if (condition2)
  return b;
if (condition3)
  return c;

// Insert all your code for the general case

比:

int returnValue;    
if (!condition) {
  if (!condition2) {
    if (!condition3) {
      // Insert your code here
    }
    else {
      returnValue = c;
    }
    returnValue = b;  // Where am I now?
  }
  returnValue = a;
}
return returnValue;

然后,当您决定 a 中的返回值时,您也会遇到这种情况switch

switch (a)
{
  case 1: return 10;
  case 2: return 20;
  case 3: return 40;
  default: return 50;
}

而不是:

int returnValue;
switch (a)
{
  case 1: returnValue = 10; break;
  case 2: returnValue = 20; break;
  case 3: returnValue = 40; break;
  default: returnValue = 50; break;
}
return returnValue; // Where is the clarity gained?
于 2012-10-05T11:54:18.047 回答
1

此外,多个退出可能是一些性能问题:当处理器运行当前命令时,它会在同一时钟滴答处处理几个下一个命令并对其执行一些操作。所以,如果你的代码有多个出口,像这样:

if (condition)
  return a;
DoSomething();
if (condition2)
  return b;

并且第一个条件为真,提取DoSomething()命令将无用。实际上,使用分支预测它仍然可以,但无论如何最好记住这一点。

于 2012-10-05T12:03:09.680 回答
0

如果您想实现这一点,您可以将返回值存储在变量中,但事实并非如此。您可以在同一个函数中有多个返回而不会出现问题。

于 2012-10-05T11:41:46.570 回答
-3

我个人并不反对提前退出,但我会提出 SingerOfTheFall 的第三种替代方案以供考虑。

好处:

  • 正常的代码流(即非错误)干净地流过代码的顶部
  • 没有机会使一个“某事”失败并传递另一个“某事”无意中执行代码块
  • 您可以在代码块上强制执行范围;包括在退出范围时清理代码子块中使用的东西

坏处:

  • 缩进可以加起来(尽管这可以通过分成子功能来减轻)
  • 如果您的编辑器中没有大括号匹配,则可能很难将错误与失败条件匹配
整数 foo()
{
    int 错误代码 = 0;
    如果(!某事){
        //100行代码
        如果(!某事){
            //100行代码
            如果(!某事){
                //100行代码
            }
            别的 {
                错误代码 = -1;
            }
        }
        别的 {
            错误代码 = 11;
       }
    }
    别的 {
        错误代码 = 1;
    }
    返回错误代码;
}
于 2012-10-05T12:04:04.180 回答