C ++代码中的双重否定
我刚刚进入一个具有相当庞大代码库的项目。
我主要处理C ++和他们写的很多代码使用双重否定他们的布尔逻辑。
if (!!variable && (!!api.lookup("some-string"))) { do_some_stuff(); }
我知道这些人是聪明的程序员,显然他们不是偶然的。
我不是经验丰富的C ++专家,我唯一的猜测是为什么他们这样做是因为他们想要绝对肯定被评估的值是实际的布尔表示。 所以他们否定它,然后再次否定它回到它的实际布尔值。
这是正确的,还是我错过了什么?
这是一个伎俩转换为布尔。
在某些情况下,这实际上是一个非常有用的成语。 拿这些macros(例如Linux内核)。 对于海湾合作委员会,他们的实施如下:
#define likely(cond) (__builtin_expect(!!(cond), 1)) #define unlikely(cond) (__builtin_expect(!!(cond), 0))
他们为什么要这样做? GCC的__builtin_expect
将其参数视为long
而不是bool
,因此需要进行某种forms的转换。 由于他们不知道当他们写这些macros时什么是cond
,所以最简单的就是使用!!
成语。
他们也许可以通过比较0来做同样的事情,但是在我看来,做双重否定实际上是更直接的,因为这是最接近于C所投的。
这个代码也可以在C ++中使用,这是一个最常见的事情。 如果可能的话,在C和C ++中都可以使用。
编码器认为它会将操作数转换为布尔值,但是因为&&的操作数已经隐含地转换为布尔值,所以它是完全多余的。
这是一种避免写入的技术(variables!= 0) – 即从任何types转换为布尔值。
像这样的IMO代码在需要维护的系统中没有地位 – 因为它不是立即可读的代码(因此首先是问题)。
代码必须清晰可辨 – 否则,您将为未来留下一笔债务遗产 – 因为需要花费一些时间才能理解无用的错误。
是的,这是正确的,没有你不错过的东西。 !!
是一个转换到布尔。 看到这个问题更多的讨论。
它侧面编译器警告。 尝试这个:
int _tmain(int argc, _TCHAR* argv[]) { int foo = 5; bool bar = foo; bool baz = !!foo; return 0; }
'bar'行会在MSVC ++上生成一个“强制值”,用于“bool'true'或'false'(性能警告)”,但是'baz'行可以很好地隐藏起来。
是运营商! 超载?
如果没有,他们可能会这样做,将variables转换为布尔值而不会产生警告。 这绝对不是一个标准的做事方式。
传统C开发人员没有布尔types,所以他们经常#define TRUE 1
和#define FALSE 0
,然后使用任意数字数据types进行布尔比较。 现在我们有了bool
,当使用数字types和布尔types混合进行某些types的赋值和比较时,许多编译器会发出警告。 这两个用法在使用旧代码时最终会发生冲突。
要解决此问题,一些开发人员使用以下布尔标识: !num_value
返回bool true
如果num_value == 0
; 否则为false
。 如果num_value == 0
,则!!num_value
返回bool false
; 否则为true
。 单个否定就足以将num_value
转换为bool
; 然而,双重否定对恢复布尔expression式的原始意义是必要的。
这种模式被称为成语 ,即熟悉该语言的人们常用的东西。 因此,我并不认为它是一种反模式,就像static_cast<bool>(num_value)
。 演员可能会给出正确的结果,但是一些编译器会发出性能警告,所以您仍然必须解决这个问题。
解决这个问题的另一种方式是说, (num_value != FALSE)
。 我也可以,但是总的来说, !!num_value
没有那么冗长,可能会更清晰,而且在你第二次看到它的时候就不会感到困惑。
正如马辛所说,如果运营商超载,这可能会很重要。 否则,在C / C ++中,除非您正在执行以下某项操作,否则无关紧要:
-
直接比较
true
(或者像C一样的TRUE
macros),这几乎总是一个坏主意。 例如:if (api.lookup("some-string") == true) {...}
-
你只是想把某些东西转换成一个严格的0/1值。 在C ++中,对
bool
的赋值将隐含地执行(对于那些可以隐式转换为bool
东西)。 在C中,如果你正在处理一个非boolvariables,这是我见过的一个成语,但是我更喜欢(some_variable != 0)
变体。
我认为在一个更大的布尔expression式的情况下,它只是混乱的事情。
如果variables是对象types,它可能有! 运算符定义,但不能转换为布尔型(或者更糟糕的是,用不同的语义转换为int型转换为int型。调用!运算符两次导致转换为bool,即使在奇怪的情况下也是如此。
!!
被用来处理原始的C ++没有布尔types(因为没有C)。
示例问题:
在if(condition)
里面, condition
需要像double, int, void*
等types那样求值,但是不要bool
因为它还不存在。
说一个类存在int256
(256位整数)和所有整数转换/强制转换过载。
int256 x = foo(); if (x) ...
要testingx
为“真”或非零, if (x)
将x
转换为某个整数, 然后评估该int
是否为非零。 (int) x
典型重载只会返回(int) x
的LSbits。 if (x)
然后只testingx
的LS位。
但是C ++有!
运营商。 重载的!x
通常会评估!x
所有位。 因此, if (!!x)
则返回非反转逻辑。
参考在if()语句中评估条件时,旧版本的C ++是否使用类的“int”运算符?
这是正确的,但在C中,这里没有意义 – “如果”和“&&”会以相同的方式对待expression式而不用“!!”。
在C ++中这样做的原因是,'&&'可能被重载。 但是,那么'!'也是如此,所以它不能保证你得到一个bool,而不需要查看variable
types和api.call
的代码。 也许有更多C ++经验的人可以解释; 也许这意味着作为一种纵深防御措施,而不是保证。
也许程序员在想这样的事情
!! myAnswer是布尔值。 在上下文中,它应该是布尔型的,但我只是喜欢砰的一声,以确保,因为曾经一度有一个神秘的bug咬我,砰的一声,我杀了它。
这可能是一个双重伎俩的例子,请参阅安全布尔成语的更多细节。 这里我总结一下文章的第一页。
在C ++中,有许多方法可以为类提供布尔testing。
operator bool
转换运算符是一个明显的方法。
// operator bool version class Testable { bool ok_; public: explicit Testable(bool b=true):ok_(b) {} operator bool() const { // use bool conversion operator return ok_; } };
我们可以testingclass级,
Testable test; if (test) std::cout << "Yes, test is working!\n"; else std::cout << "No, test is not working!\n";
然而, opereator bool
被认为是不安全的,因为它允许无意义的操作,如test << 1;
或者int i=test
。
使用
operator!
更安全是因为我们避免了隐式转换或重载问题。
实施是微不足道的,
bool operator!() const { // use operator! return !ok_; }
testingTestable
对象的两种惯用方法是
Testable test; if (!!test) std::cout << "Yes, test is working!\n"; if (!test2) { std::cout << "No, test2 is not working!\n";
第一个版本if (!!test)
是有人所说的双重伎俩 。