ChatGPT解决这个技术问题 Extra ChatGPT

if 语句 - 短路评估与可读性

有时,if 语句可能相当复杂或很长,因此为了便于阅读,最好在 if 之前提取复杂的调用。

例如这个:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

进入这个

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(提供的例子不是那么糟糕,它只是为了说明......想象其他带有多个参数的调用等)

但是通过这次提取,我失去了短路评估(SCE)。

我真的每次都失去 SCE 吗?是否存在允许编译器“优化它”并仍然提供 SCE 的情况?有没有办法在不丢失 SCE 的情况下保持第二个片段的改进可读性?

实践表明,您将在此处或其他地方看到的大多数关于性能的答案在大多数情况下都是错误的(4 错误 1 正确)。我的建议是始终进行分析并自己检查,您将避免“过早优化”并学习新东西。
@MarekR 不仅与性能有关,还与 OtherCunctionCall 中可能的副作用有关...
@David 在提及其他网站时,指出 cross-posting is frowned upon 通常会有所帮助
如果可读性是您最关心的问题,请不要在 if 条件内调用具有副作用的函数
潜在的亲密选民:再次阅读问题。第 (1) 部分不是基于意见的,而第 (2) 部分可以很容易地通过编辑删除对任何所谓的“最佳实践”的引用而不再是基于意见的,正如我即将做的那样。

H
Horia Coman

一种自然的解决方案如下所示:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

这具有易于理解、适用于所有情况以及具有短路行为的优点。

这是我最初的解决方案:方法调用和 for 循环体中的一个很好的模式如下:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

一个人可以获得与短路评估相同的良好性能优势,但代码看起来更具可读性。


@relaxxx:我明白了,但是“在 if 之后要做更多的事情”也表明您的函数或方法太大,应该分成更小的函数或方法。它并不总是最好的方法,但经常是!
这违反了白名单原则
@JoulinRouge:有趣,我从未听说过这个原则。我自己更喜欢这种“短路”方法,因为它有利于可读性:它减少了缩进并消除了在缩进块之后发生某些事情的可能性。
它更具可读性吗?正确命名 b2,您会得到 someConditionAndSomeotherConditionIsTrue,但意义不大。此外,在此练习期间,我必须在我的心理堆栈中保留一堆变量(并且直到我停止在此范围内工作)。我会选择 SJuan76 的 2 号解决方案,或者只是将整个事情放在一个函数中。
我还没有阅读所有评论,但经过快速搜索后,我没有发现第一个代码片段的一大优势,即调试。将内容直接放入 if 语句中,而不是事先将其分配给变量然后使用该变量,这会使调试变得比需要的更加困难。使用变量还允许在语义上将值分组在一起,从而提高可读性。
A
AmigoJack

我倾向于将条件分解为多行,即:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

即使在处理多个运算符 (&&) 时,您也只需要使用每对括号提前缩进。 SCE 仍然有效 - 无需使用变量。多年来,以这种方式编写代码使其对我来说更具可读性。更复杂的例子:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

S
Some programmer dude

如果您有很长的条件链以及保持一些短路的条件,那么您可以使用临时变量来组合多个条件。以您为例,可以这样做

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

如果你有一个支持 C++11 的编译器,你可以使用 lambda expressions 将表达式组合成函数,类似于上面的:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

S
SJuan76

1) 是的,您不再拥有 SCE。否则,你会有那个

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

以一种或另一种方式工作,具体取决于稍后是否有 if 语句。太复杂了。

2)这是基于意见的,但对于相当复杂的表达,你可以这样做:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

如果它太复杂,显而易见的解决方案是创建一个计算表达式并调用它的函数。


K
KIIV

您还可以使用:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

SCE 将起作用。

但它并不比例如更具可读性:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

我不热衷于将布尔值与按位运算符结合起来。这对我来说似乎不是很好的类型。通常我使用看起来最易读的任何东西,除非我工作的级别非常低并且处理器周期数。
我专门使用了 b = b || otherComplicatedStuff(); 并且 @SargeBorsch 进行了编辑以删除 SCE。感谢您注意到我@Ant 的这一变化。
s
songyuanyao

1) 我真的每次都失去 SCE 吗?编译器是否允许“优化”某些场景并仍提供 SCE?

我不认为这样的优化是允许的;尤其是 OtherComplicatedFunctionCall() 可能会有一些副作用。

2)在这种情况下,最佳做法是什么?是否只有可能(当我想要 SCE 时)直接在内部拥有我需要的所有内容,并且“只是将其格式化为尽可能可读”?

我更喜欢将它重构为一个函数或一个具有描述性名称的变量;这将保留短路评估和可读性:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

当我们基于 SomeComplicatedFunctionCall()OtherComplicatedFunctionCall() 实现 getSomeResult() 时,如果它们仍然很复杂,我们可以递归地分解它们。


我喜欢这个,因为你可以通过给包装函数一个描述性的名称来获得一些可读性(尽管可能不是 getSomeResult),太多其他答案并没有真正增加任何价值
H
Hatted Rooster

1) 我真的每次都失去 SCE 吗?编译器是否允许“优化”某些场景并仍提供 SCE?

不,你没有,但它的应用方式不同:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

在这里,如果 SomeComplicatedFunctionCall() 返回 true,编译器甚至不会运行 OtherComplicatedFunctionCall()

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

在这里,两个函数运行,因为它们必须存储在 b1b2 中。 Ff b1 == true 然后 b2 不会被评估 (SCE)。但 OtherComplicatedFunctionCall() 已经运行。

如果 b2 没有在其他任何地方使用,编译器可能足够聪明,可以在 if 函数没有可观察到的副作用的情况下内联函数调用。

2)在这种情况下,最佳做法是什么?是否只有可能(当我想要 SCE 时)直接在内部拥有我需要的所有内容,并且“只是将其格式化为尽可能可读”?

那要看。您是否需要 OtherComplicatedFunctionCall() 因为副作用或函数的性能影响很小而运行,那么您应该使用第二种方法来提高可读性。否则,通过第一种方法坚持 SCE。


l
levilime

短路的另一种可能性并在一个地方具有条件:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

您可以将循环放入函数中,让函数接受条件列表并输出布尔值。


@Erbureth 不,他们不是。数组的元素是函数指针,在循环中调用函数之前它们不会被执行。
谢谢 Barmar,但我做了一个编辑,Erbureth 是对的,在编辑之前(我认为我的编辑会更直接地在视觉上传播)。
r
radical7

很奇怪:当没有人提到代码中注释的用法时,您正在谈论可读性:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

最重要的是,我总是在我的函数前面加上一些评论,关于函数本身,关于它的输入和输出,有时我会举一个例子,你可以在这里看到:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

显然,用于注释的格式可能取决于您的开发环境(Visual Studio、Eclipse 下的 JavaDoc,...)

就 SCE 而言,我认为您的意思是:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

b
br0lly

如果您在一家公司工作并且您的代码将被其他人阅读,那么可读性是必要的。如果您为自己编写程序,那么您是否想为了可理解的代码而牺牲性能取决于您。


请记住,“六个月后的你”肯定是“其他人”,而“明天的你”有时可能是。在我有一些确凿的证据表明存在性能问题之前,我永远不会为了性能而牺牲可读性。