【问题标题】:Is there an Non-Short circuited logical "and" in C++?C++ 中是否存在非短路逻辑“与”?
【发布时间】:2010-12-18 00:48:29
【问题描述】:

tl;dr:C++ 中是否存在非短路逻辑与(类似于 &&)?

我有 2 个要调用的函数,并使用返回值来计算第三个复合函数的返回值。问题是我总是希望这两个函数都进行评估(因为它们输出有关系统状态的日志信息)

IE:

bool Func1(int x, int y){
  if( x > y){
    cout << "ERROR- X > Y" << endl;
  }
}
bool Func2(int z, int q){
  if( q * 3 < z){
    cout << "ERROR- Q < Z/3" << endl;
  }
}
bool Func3(int x, int y, int z, int q){
  return ( Func1(x, y) && Func2(z, q) );
}

当然,函数中的条件并不是那么简单,是的,我意识到我可以使用临时变量来存储两个函数的返回,然后在临时变量上执行“短路”逻辑变量,但我想知道是否有一种“优雅的”语言解决方案可以在 Func3 中保持单行返回,同时仍然从两个函数获取日志消息。


回复摘要:

“按位”运算符 |和 & 可用于获得效果,但前提是返回类型为 bool。我在 ANSI C++ 规范中没有提到这一点。据我所知,这是可行的,因为“bool”被转换为int(true = 1,false = 0),然后使用按位运算符,然后将其转换回bool。

也可以使用运算符“+”和“*”。这在 ANSI C++ 规范中没有提到,但可能由于与上述相同的原因而起作用。 "+" 给出 "or" 因为 true 被转换为 1,然后除了 0 之外的任何东西都被转换回 true。 "*" 适用于 "and" 因为 1 (true) * 0 (false) == 0(false) 和 1(true) * 1(true) == 1(true)

这两者似乎都依赖于隐式类型转换为整数然后再返回布尔值。这两者都可能会搞砸试图维护代码的人。

其他回答归结为“只使用临时人员”或“实施你自己的”,这不是问题所在。目标是查看是否已经在 C++ 标准中实现了一个运算符来执行此操作。

【问题讨论】:

  • 我只会使用临时的。它至少与调用 if() 中的函数一样清晰,并且您不必寻找晦涩的方法来使您的方法起作用。
  • “优雅:在运动或执行中展现轻松的美感和简洁性”如果临时变量轻松(编写和理解)并且简单地实现了您的目标,那么它是否优雅?
  • 我认为您可以只使用临时 volatile 变量,如:volatile uchar retVal1 = Func1(); volatile uchar retVal2 = Func2(); if(retVal1 &amp;&amp; retVal2) { // Do my stuff } 这可确保程序评估两个变量(也称为调用两个函数),因此您可以在您的条件下使用它们。
  • @davidanderle,不需要 volatile。 Func1() 和 Func2() 无论如何都会被调用。

标签: c++ operators logical-operators


【解决方案1】:

&amp; 运算符对bool 操作数执行逻辑“与”运算并且没有短路。

这不是一个序列点。您不能依赖操作数的评估顺序。但是,可以保证两个操作数都被求值。

我确实建议这样做。使用临时变量是更好的解决方案。不要为了“聪明的代码”而牺牲可读性。

【讨论】:

  • 我很确定这是进行非短路布尔运算的记录标准方法——只需使用按位运算符。这也适用于 OR(单个 | 符号)。
  • 您最好对任何这样做的代码发表大量评论 - 任何具有一年以上经验的 C 开发人员都会看到 expr1 &amp; expr2 并“修复”它。使用临时变量是一个更好的解决方案。
  • 当然,在这里迂腐,OP 要求非短路 逻辑 AND,而不是按位 AND。
  • @Matt G:这适用于布尔值,但一般不适用。 1 &amp;&amp; 2 产生true,而1 &amp; 2 产生0
  • 感谢您的回复。我将在实际代码中使用临时变量,因为我不喜欢依赖“&”的“双重含义”。这更像是一个“这似乎比它必须的更多行......也许有一个我不知道的简洁易懂的语言功能”问题,谷歌让我失望了。
【解决方案2】:

是的,我意识到我可以使用临时变量来存储两个函数的返回值,然后对临时变量执行“短路”逻辑,但我想知道是否有“优雅”的语言解决方案保持 Func3 中的单行返回,同时仍从两个函数获取日志消息。

这将是“优雅”的解决方案:)。依赖评估顺序的副作用对于下一个进入您的项目的开发人员来说远非优雅、容易出错且难以理解。当然,依赖 副作用 与下面的 sn-p 类似,这是一个完全合乎逻辑且有效的用例,仅依赖于评估顺序:

if ( obj != NULL && obj->foo == blah ) { /* do stuff */ }

【讨论】:

  • 我还建议将 cmets 放在“必须执行这两个函数”之类的地方,否则其中一位乐观的程序员会说“哦,这些临时变量不需要 *inline”.. . 无论哪种方式(& 或 temp vars)都需要 cmets 来阐明它们的用法
  • 当然,依赖副作用很可能是这里出了问题的另一个迹象。
  • Re “依赖 AND 运算符的求值顺序对于下一个进入你的项目的开发人员来说远非优雅、容易出错且难以理解。”:有很多案例我建议依赖 && 评估顺序,而不是不必要地嵌套 if 语句。典型的例子是if (pSomething != NULL &amp;&amp; pSomething-&gt;someField) ...
  • @Mehrdad:是的,我也这样做,但这种情况并不完全一样。您是在说“如果这不是真的,请不要评估第二部分”,OP 说“我希望所有这些都进行评估,因为下一个函数调用取决于一些副作用,即使 AND 不需要评估表达式n+1...2"。也许我可以用不同的措辞来解决这个特定的用途。
  • 也许最后的评论措辞不佳:)。我只是想表达一个事实,即您的示例是对 && 运算符的非常干净和清晰的使用。 “只有当这是真的时才这样做”。这就说得通了。帖子中的示例没有这些语义。
【解决方案3】:

是的,有内置的操作符可以做到这一点。 + 执行非短路 OR,* 执行 AND。

#include <iostream>
using namespace std;

void print(bool b)
{
    cout << boolalpha << b << endl;
}

int main() 
{
    print(true + false);
    print(true * false);
}

输出:

是的

【讨论】:

  • 我不知道我是否会在没有大注释的情况下在生产代码中这样做,但它仍然很酷。
  • 非常酷!这就是我问这个问题的原因:不一定要在生产代码中使用它,而是要学习一些东西。
  • 这不是“酷”,而是“布尔”。布尔代数...sigh +/* 操作和 AND/OR 之间的意识形态同态性已经众所周知了 100 年?
  • @vaxquis 可能,但许多语言对布尔值和整数的处理方式相同,只是要求将 if (x) ... 形式的语句扩展为 if (x != 0) ...。例如,在 Python 2.x 中,print(True + True) 输出“2”。
  • 1 || -1 评估为真,1 + -1 评估为假,还是我遗漏了什么? !!a & !!b or,对于一个或,!!a | 怎么样? !!b ?
【解决方案4】:

你可以自己写。

bool LongCircuitAnd( bool term1, bool term2 ) { return term1 && term2; }

bool Func3(int x, int y, int z, int q){
  return LongCircuitAnd( Func1(x,y), Func2(z,q) ); 

如果你想非常花哨,你甚至可以内联它!!!

好的,好的,如果你真的不想要调用函数的可怕开销。

bool Func3(int x, int y, int z, int q){
  return ((int)Func1(x,y)) * ((int)Func2(z,q)); 

但我不认为这很优雅。可以想象,一个过于聪明的编译器可能会短路这个......

【讨论】:

  • 您的 LongCircuitAnd 函数不只是简化为常规短路 AND(因为您返回 bool1 &amp;&amp; bool2)?
  • 我再次查看了这个...LongCircuitAnd 有效,因为您传递给它的参数是函数调用。这些函数(它们都是)在您进入LongCircuitAnd 的主体之前执行。但是,这种方法具有误导性,因为您仍然使用短路运算符;由于您已经看到了这两个函数的副作用,因此您不会注意到短路。
  • @ThisSuitIsBlackNot - 返回表达式是短路的,但是两个表达式都必须完全计算才能作为参数传递给函数,所以在到达 return 语句之前,两个“子表达式”都有进行了全面评估。
  • @Sanjaya - 除非编译器能够证明它想要短路的东西没有副作用,否则编译器无法短路您的乘法替代方案。在这种情况下,是否发生短路无关紧要。
  • 并且,在这个解决方案中,调用 Func1 和 Func2 的顺序是不确定的。由于示例显示它们都记录到同一个流,这可能是一个问题。
【解决方案5】:

如果你想使用临时变量,但保持返回单个语句,你可以使用逗号运算符:

return (b1=Func1()), (b2=Func2()), (b1&&b2);

逗号运算符强制一个序列点,因此每个运算符都会计算其左操作数,丢弃结果,然后计算其右操作数。

另一种可能性,但我倾向于反对,是让这两个函数返回一个重载“&&”运算符的类型。由于重载运算符调用一个函数,它总是计算 both 操作数,即使在内置运算符不计算的情况下(如 &&)——通常这是一个问题,但在这种情况下它是正是你想要的:

class mybool { 
    bool value;
public:
    bool operator&&(mybool const &other) const { 
        return value && other.value;
    }
};

mybool Func1(int, int);
mybool Func2(int, int);

bool Func3(int x, int y, int z, int q) { 
    return Func1(x, y) && Func2(z,q);
}

虽然这行得通,但在我看来它有点太“聪明”了——这对大多数读者来说根本不明显。 mybool 的不同名称可能会有所帮助,但我想不出一个既能很好地反映意图又不会变得如此冗长的名称,这将是净损失。

【讨论】:

  • 我喜欢这个。它使评估保持有序。
【解决方案6】:

是的。 operator&amp;&amp;operator|| 的重载版本不会短路 - 即使左侧操作数“确定”结果,它们也会评估两个操作数......(Source

话虽如此,请不要超载operator&amp;&amp;operator||。善待你的维护程序员,他们会查看&amp;&amp;|| 并假设他们确实短路了。

【讨论】:

  • 这就是为什么我认为这是一个允许&amp;&amp;|| 运算符重载的设计错误。
  • 不,它对于类似 lambda 的功能是必不可少的,并且最终的评估(稍后定义)是短路的。出乎意料,但非常有用。
【解决方案7】:

一个近乎通用但经常没有记录的非标准运算符被引入正是为了这个目的,由 GCC 与 x ?: y 一起开创了(x 如果非零,否则 y),现在很遗憾地被删除了&gt;?&lt;? 最小/最大运算符及其复合赋值形式(请参阅 http://gcc.gnu.org/onlinedocs/gcc/Deprecated-Features.html)。可悲的是,&amp;&amp;&amp; 已经在使用中,他们似乎一直在寻找合适的字符序列,但这只是我的观点——欢迎任何历史解释来解释为什么会选择它.

因此,虽然它目前不像许多其他运算符那样广为人知,但 &gt;! 运算符(恰当但无聊地称为“长回路与”,但通俗地称为“更大的结”知道)被大多数 C 和 C++ 编译器(包括 GCC 甚至 MSVC++)添加来满足这个要求:

bool f1() { ... }
bool f2() { ... }

...
bool f3() { return f1() >! f2(); }

试一试;-)。

【讨论】:

  • 正如(现已删除)答案指出的那样,这实际上不是一个新的运算符(就像--&gt; 不是),而只是一个格式奇怪的a &gt; !b,其工作方式与@ 相同987654331@ 除了没有短路,只要ab 都不是负数。
猜你喜欢
  • 1970-01-01
  • 2012-03-05
  • 2019-12-06
  • 2020-05-11
  • 2018-05-21
  • 1970-01-01
  • 2013-10-18
  • 1970-01-01
  • 2015-04-11
相关资源
最近更新 更多