if 语句 - 短路评估与可读性
if statement - short circuit evaluation vs readability
有时,if
语句可能相当复杂或较长,因此为了可读性,最好在 if
.
之前提取复杂的调用
例如这个:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
进入这个
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
(提供的示例并非那个不好,它只是为了说明...想象其他具有多个参数的调用等)
但是通过这次提取,我丢失了短路评估 (SCE)。
- 我真的每次都失去SCE吗?是否存在允许编译器 "optimize it" 并仍然提供 SCE 的情况?
- 有没有办法在不丢失 SCE 的情况下保持第二个片段的改进可读性?
一个自然的解决方案如下所示:
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
短路评估的性能优势同样出色,但代码看起来更具可读性。
1) 是的,您不再拥有 SCE。否则,您将拥有
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
以一种方式或另一种方式工作,具体取决于稍后是否有 if
语句。太复杂了。
2) 这是基于意见的,但对于相当复杂的表达式,您可以这样做:
if (SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()) {
如果它太复杂,显而易见的解决方案是创建一个计算表达式并调用它的函数。
1) Do I really lose SCE every time? Is compiler is some scenario
allowed to "optimize it" and still provide SCE?
不,你不知道,但它的应用方式不同:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
在这里,编译器甚至不会 运行 OtherComplicatedFunctionCall()
if SomeComplicatedFunctionCall()
returns true.
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
这里,两个函数都会运行,因为它们必须存储到b1
和b2
中。 Ff b1 == true
那么 b2
将不会被评估 (SCE)。但是 OtherComplicatedFunctionCall()
已经 运行。
如果 b2
没有在其他任何地方使用,编译器 可能 足够聪明,如果函数没有可观察到的副作用,则将函数调用内联到 if 中。
2) What is the best practice in such situation? Is it only possibility
(when I want SCE) to have all I need directly inside if and "just
format it to be as readable as possible" ?
这取决于。
你 需要 OtherComplicatedFunctionCall()
到 运行 因为副作用或函数的性能影响很小,那么你应该使用第二种方法来提高可读性。否则,通过第一种方法坚持使用 SCE。
如果您有很长的条件链并且要保持一些短路,那么您可以使用临时变量来组合多个条件。以你的例子为例,可以这样做
bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }
如果您有支持 C++11 的编译器,您可以使用 lambda expressions 将表达式组合成函数,类似于上面的内容:
auto e = []()
{
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};
if (e() && some_other_expression) { ... }
如果你在一家公司工作并且你的代码将被其他人阅读,那么可读性是必要的。如果你自己写程序,为了代码的可理解性而牺牲性能,那是你自己的事。
1) Do I really lose SCE every time? Is compiler is some scenario allowed to "optimize it" and still provide SCE?
我认为这样的优化是不允许的;特别是 OtherComplicatedFunctionCall()
可能会有一些副作用。
2) What is the best practice in such situation? Is it only possibility (when I want SCE) to have all I need directly inside if and "just format it to be as readable as possible" ?
我更喜欢将其重构为一个函数或一个具有描述性名称的变量;这将保留短路评估和可读性:
bool getSomeResult() {
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}
...
if (getSomeResult())
{
//do stuff
}
并且当我们基于 SomeComplicatedFunctionCall()
和 OtherComplicatedFunctionCall()
实现 getSomeResult()
时,如果它们仍然很复杂,我们可以递归分解它们。
您还可以使用:
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()
)
我倾向于将条件分解为多行,即:
if( SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()
) {
即使在处理多个运算符 (&&) 时,您也只需要在每对括号中提前缩进。 SCE 仍然有效——无需使用变量。多年来,以这种方式编写代码对我来说更易读。更复杂的例子:
if( one()
||( two()> 1337
&&( three()== 'foo'
|| four()
)
)
|| five()!= 3.1415
) {
另一种可能短路并有一处条件:
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
您可以将循环放入一个函数中,让该函数接受条件列表并输出一个布尔值。
很奇怪:当没有人提到代码中注释的使用时,你在谈论可读性:
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) {
...
}
有时,if
语句可能相当复杂或较长,因此为了可读性,最好在 if
.
例如这个:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
进入这个
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
(提供的示例并非那个不好,它只是为了说明...想象其他具有多个参数的调用等)
但是通过这次提取,我丢失了短路评估 (SCE)。
- 我真的每次都失去SCE吗?是否存在允许编译器 "optimize it" 并仍然提供 SCE 的情况?
- 有没有办法在不丢失 SCE 的情况下保持第二个片段的改进可读性?
一个自然的解决方案如下所示:
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
短路评估的性能优势同样出色,但代码看起来更具可读性。
1) 是的,您不再拥有 SCE。否则,您将拥有
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
以一种方式或另一种方式工作,具体取决于稍后是否有 if
语句。太复杂了。
2) 这是基于意见的,但对于相当复杂的表达式,您可以这样做:
if (SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()) {
如果它太复杂,显而易见的解决方案是创建一个计算表达式并调用它的函数。
1) Do I really lose SCE every time? Is compiler is some scenario allowed to "optimize it" and still provide SCE?
不,你不知道,但它的应用方式不同:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
在这里,编译器甚至不会 运行 OtherComplicatedFunctionCall()
if SomeComplicatedFunctionCall()
returns true.
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
这里,两个函数都会运行,因为它们必须存储到b1
和b2
中。 Ff b1 == true
那么 b2
将不会被评估 (SCE)。但是 OtherComplicatedFunctionCall()
已经 运行。
如果 b2
没有在其他任何地方使用,编译器 可能 足够聪明,如果函数没有可观察到的副作用,则将函数调用内联到 if 中。
2) What is the best practice in such situation? Is it only possibility (when I want SCE) to have all I need directly inside if and "just format it to be as readable as possible" ?
这取决于。
你 需要 OtherComplicatedFunctionCall()
到 运行 因为副作用或函数的性能影响很小,那么你应该使用第二种方法来提高可读性。否则,通过第一种方法坚持使用 SCE。
如果您有很长的条件链并且要保持一些短路,那么您可以使用临时变量来组合多个条件。以你的例子为例,可以这样做
bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }
如果您有支持 C++11 的编译器,您可以使用 lambda expressions 将表达式组合成函数,类似于上面的内容:
auto e = []()
{
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};
if (e() && some_other_expression) { ... }
如果你在一家公司工作并且你的代码将被其他人阅读,那么可读性是必要的。如果你自己写程序,为了代码的可理解性而牺牲性能,那是你自己的事。
1) Do I really lose SCE every time? Is compiler is some scenario allowed to "optimize it" and still provide SCE?
我认为这样的优化是不允许的;特别是 OtherComplicatedFunctionCall()
可能会有一些副作用。
2) What is the best practice in such situation? Is it only possibility (when I want SCE) to have all I need directly inside if and "just format it to be as readable as possible" ?
我更喜欢将其重构为一个函数或一个具有描述性名称的变量;这将保留短路评估和可读性:
bool getSomeResult() {
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}
...
if (getSomeResult())
{
//do stuff
}
并且当我们基于 SomeComplicatedFunctionCall()
和 OtherComplicatedFunctionCall()
实现 getSomeResult()
时,如果它们仍然很复杂,我们可以递归分解它们。
您还可以使用:
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()
)
我倾向于将条件分解为多行,即:
if( SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()
) {
即使在处理多个运算符 (&&) 时,您也只需要在每对括号中提前缩进。 SCE 仍然有效——无需使用变量。多年来,以这种方式编写代码对我来说更易读。更复杂的例子:
if( one()
||( two()> 1337
&&( three()== 'foo'
|| four()
)
)
|| five()!= 3.1415
) {
另一种可能短路并有一处条件:
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
您可以将循环放入一个函数中,让该函数接受条件列表并输出一个布尔值。
很奇怪:当没有人提到代码中注释的使用时,你在谈论可读性:
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) {
...
}