C++ 中是否有一个非短路逻辑“与”运算符?

37

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

我有两个函数要调用,并使用它们的返回值来确定第三个组合函数的返回值。问题是我希望这两个函数始终都被计算(因为它们输出有关系统状态的日志信息)。

例如:

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中的单行返回,并仍然从两个函数获取日志消息。
回应摘要:
“按位”运算符|和&可以用于实现效果,但只适用于返回类型为布尔型的情况。我没有在ANSI C ++规范中找到这方面的提及。据我所知,这可行是因为“bool”转换为int(true = 1,false = 0),然后使用按位运算符,然后再将其转换回bool。
运算符“+”和“*”也可以使用。尽管这在ANSI C ++规范中没有提到,但可能是因为与上述相同的原因。由于true被转换为1,“+”给出“或”;任何非零值都被转换回true。“*”适用于“and”,因为1(true)* 0(false)== 0(false),1(true)* 1(true)== 1(true)。
这两种方法似乎都依赖于隐式类型转换为整数,然后再转换回布尔值。这两种方法都可能会使维护代码的人困惑。
其他反应可以归结为“只使用临时变量”或“实现自己”,这并不是问题的答案。目标是看看是否已经在C ++标准中实现了运算符来解决这个问题。

4
我会只使用临时变量。这至少和在if()语句中调用函数一样清晰,而且你不必寻找晦涩的方法使你的方法正常工作。 - Ron Warholic
优雅:在运动或执行中展现出毫不费力的美丽和简单。如果一个临时变量既容易编写又容易理解,并且能够简单地实现您的目标,那么它是否优雅呢? - Roger Pate
我认为你可以像这样使用临时易失变量: volatile uchar retVal1 = Func1(); volatile uchar retVal2 = Func2(); if(retVal1 && retVal2) { // Do my stuff } 这确保程序评估两个变量(即调用两个函数),因此您可以在条件中使用它们。 - davidanderle
@davidanderle,不需要使用volatile。Func1()和Func2()将会被调用。 - Alex Che
7个回答

41

& 运算符对于 bool 操作数执行逻辑 "and" 操作,不支持短路。

它不是序列点。您不能依赖操作数的求值顺序。但是,保证会对两个操作数进行求值。

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


1
我相当确定这是文档记录的标准方法来执行非短路布尔运算 - 只需使用位运算符。对于OR(单个|符号)也是如此。 - Matt G.
34
最好在执行此操作的任何代码周围添加一个巨大的注释 - 任何有超过一年经验的C开发人员都会看到expr1&expr2并“修复”它。使用临时变量是更好的解决方案。 - Graeme Perrow
3
当然,严谨地说,原帖要求的是非短路逻辑 AND,而不是位运算 AND。 - Ed S.
9
对于布尔值是可行的,但不适用于一般情况。1 && 2得到true,而1 & 2得到0 - David Thornley
1
感谢您的回复。我将在实际代码中使用临时变量,因为我不喜欢依赖“&”的“双重含义”。这更多是一个“这似乎比必须要多一些行...也许有一种简洁易懂的语言特性我不知道”的问题,但谷歌并没有帮到我。 - Luciano
显示剩余5条评论

23
并且,我意识到我可以使用临时变量来存储这两个函数的返回值,然后对临时变量执行"短路"逻辑,但我想知道是否有一种 "优雅"的语言解决方案,可以使Func3保持单行返回的同时仍然获取两个函数的日志信息。
那将是"优雅"的解决方案:)。依赖于评估顺序的副作用远非优雅,容易出错,并且很难理解下一个进入项目的开发人员。当然,依赖于副作用与以下代码片段形成对比,后者完全符合仅依赖于评估顺序的逻辑和有效用例:
if ( obj != NULL && obj->foo == blah ) { /* do stuff */ }

4
我还建议加上注释,说明“这两个函数必须被执行”或者其他类似的内容,否则那些乐观的程序员会说:“哦,这些临时变量不需要*内联”……无论是&还是临时变量,都需要注释来澄清它们的用途。 - Earlz
1
当然,依赖副作用很可能是另一个表明这里出了问题的迹象。 - Ed S.
1
关于“依赖AND运算符的评估顺序将远非优雅,容易出错,并且难以理解下一个进入项目的开发人员。”:有很多情况下,我建议依赖&&的评估顺序而不是不必要地嵌套if语句。典型的例子是if (pSomething != NULL && pSomething->someField) ... - Mehrdad Afshari
@Mehrdad:是的,我也这样做,但这种情况并不完全相同。你是说“如果这不是真的,就不要评估第二部分”,而OP是说“我希望所有这些都能被评估,因为下一个函数调用取决于某些副作用,即使AND不需要评估表达式n+1...2”。也许我可以用不同的措辞来解决这个特定的问题。 - Ed S.
@Ed:副作用来自于日志记录行为,所以我会认为它们的意思是指日志是否成功。我觉得这很合理。 - David Thornley
显示剩余2条评论

14

是的,有一些内置运算符可以完成此操作。+ 执行非短路或(OR)操作,* 执行与(AND)操作。

#include <iostream>
using namespace std;

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

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

输出:

true

false


3
不确定我是否会在正式代码中这样做,除非有一个很大的注释,但这仍然很酷。 - deft_code
4
非常酷!这也是我提出问题的原因:并不一定要在实际代码中使用,而是为了学习一些东西。 - Luciano
1
它不是“酷”,而是“布尔”。布尔代数... 叹气 +/* 操作和 AND/OR 之间的意识形态同态已经众所周知了,大约有100年了。 - user719662
1
@vaxquis 或许是这样,但很多编程语言将布尔值和整数视为同一类型,只需要将形如 if (x) ... 的语句扩展为 if (x != 0) ...。例如,在 Python 2.x 中,print(True + True) 输出的结果是 "2"。 - Ponkadoodle
1 || -1 作为真值,1 + -1 作为假值,或者我漏掉了什么?对于 !!a & !!b 或者 ||a | ||b 这样的或运算,怎么样呢? - Jacques de Hooge
@JacquesdeHooge -1 不是一个有效的 bool 值。 - Motti

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 && bool2) 吗? - ThisSuitIsBlackNot
我重新仔细看了一下这个问题... LongCircuitAnd 能够正常工作是因为你传递给它的参数是函数调用。这些函数(两个函数)在进入 LongCircuitAnd 的主体之前就已经被执行了。然而,这种方法是具有误导性的,因为你仍然使用了短路运算符;由于你已经看到了这两个函数的副作用,所以你没有注意到短路的发生。 - ThisSuitIsBlackNot
2
@ThisSuitIsBlackNot - 返回表达式是短路的,但两个表达式必须完全评估才能作为参数传递给函数,因此在到达return语句之前,这两个“子表达式”已经被完全评估。 - Michael Burr
@Sanjaya - 编译器无法对你的乘法替代方案进行短路,除非编译器能够证明它想要短路的内容没有副作用。在这种情况下,短路是否发生都无关紧要。 - Michael Burr
我也认为,为了符合VB.NET(相对较新的)短路运算符(AndAlsoOrElse)的精神,这些函数应该被命名为类似于AndEvenIf()OrInAddition()(开玩笑)。 - Michael Burr
3
在这个解决方案中,调用Func1和Func2的顺序是不确定的。由于示例显示它们都向同一个流进行记录,这可能会引起问题。 - David Thornley

3

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

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

逗号运算符会强制执行一个序列点,因此每个逗号运算符都会评估其左操作数,丢弃结果,然后评估其右操作数。
另一种可能性是两个函数返回重载了 "&&" 运算符的类型,但我倾向于不建议这样做。由于重载的运算符会调用函数,它总是评估 两个 操作数,即使在内置运算符(如 &&)不评估的情况下也是如此 - 通常这是一个问题,但在这种情况下正是你想要的:
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取一个不同的名称可能会有所帮助,但我一时想不到一个既能很好地反映意图又不会变得过于冗长以至于成为净损失的名称。


我喜欢这个。它保持了评估的顺序。 - David Thornley

2
一个几乎普遍但常常未被记录的非标准运算符被引入,就是为了这个目的而开发的,由GCC与x ?: y(如果非零则为x,否则为y)以及现在不幸已经移除的>?<?最大/最小运算符及其复合赋值形式一起开创了这个运算符(请参见http://gcc.gnu.org/onlinedocs/gcc/Deprecated-Features.html)。不幸的是,在使用&&&之后,它们似乎已经在底部擦拭来找到一个适当的字符序列,但这只是我的意见——欢迎任何历史解释为什么会选择这个。

因此,虽然它目前不像许多其他运算符那样广为人知,但大多数C和C++编译器(包括GCC甚至MSVC++)都添加了>!运算符(正确但无聊地称为“长路与”,但对内行人来说俗称“大结”),以满足这个要求:

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

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

请试一下它;-)。

2
正如一篇(现已删除的)答案所指出的那样,这实际上不是一个新的运算符(就像 --> 一样),而只是一个奇怪格式化的 a > !b,它的工作方式与 a && b 相同,除了没有短路,只要 ab 不为负数。 - Cubic

2
是的,重载版本的operator&&operator||不会短路——即使左操作数“决定”了结果,它们也会评估两个操作数... (来源)
话虽如此,请不要重载operator&&operator||。对于查看&&||并假设它们短路的维护程序员来说,请友好一些。

这就是为什么我认为允许重载 &&|| 运算符是一个设计缺陷。 - Motti
不,这对于类似lambda的功能是必要的,并且在那里最终评估(按定义稍后)被短路。这是意料之外的,但非常有用。 - MSalters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接