C ++中的双重否定


124

我刚进入一个代码库很大的项目。

我主要处理的是C ++,他们编写的许多代码的布尔逻辑都使用双重取反。

 if (!!variable && (!!api.lookup("some-string"))) {
       do_some_stuff();
 }                                   

我知道这些人是聪明的程序员,很明显,他们并不是故意这样做的。

我不是经验丰富的C ++专家,我对他们为什么这样做的唯一猜测是,他们想绝对肯定所评估的值是实际的布尔表示形式。因此,他们将其取反,然后再次取反以使其恢复为实际的布尔值。

这是正确的,还是我缺少了什么?



这个话题已经在这里讨论过
Dima

Answers:


121

转换为bool是一个技巧。


19
我认为使用(bool)进行显式转换会更清楚,为什么要使用这个棘手的!!,因为它的键入较少?
白彦煌

27
但是,在C ++或现代C语言中,或者仅在布尔表达式中使用结果的情况下(如在问题中),它是没有意义的。当我们没有bool类型时,它很有用,有助于避免在布尔变量中1和以外存储值0
Mike Seymour 2012年

6
@lzprgmr:显式强制转换在MSVC上引起“性能警告”。使用!!!=0解决了这个问题,在这两者之间我找到了前一个清洁器(因为它可以处理更多的类型)。我也同意没有理由在相关代码中使用任何一种。
Yakov Galka'9

6
@Noldorin,我认为它可以提高可读性-如果您知道它的意思,那么它简单,整洁且合乎逻辑。
jwg 2014年

19
提高?血腥的地狱...给我一些你在抽烟的东西。
Noldorin 2014年

73

在某些情况下,这实际上是一个非常有用的习语。获取这些宏(以Linux内核为例)。对于GCC,它们的实现方式如下:

#define likely(cond)   (__builtin_expect(!!(cond), 1))
#define unlikely(cond) (__builtin_expect(!!(cond), 0))

他们为什么要这样做?GCC __builtin_expect将其参数视为long而不是bool,因此需要某种形式的转换。由于他们不知道cond何时编写这些宏,所以最简单的做法就是使用!!惯用法。

通过与0进行比较,他们可能会做同样的事情,但是在我看来,双重否定实际上更直接,因为这是C语言最接近强制转换的方法。

这段代码也可以在C ++中使用。这是最低公分母的东西。如果可能,请执行在C和C ++中均有效的操作。


我认为当您仔细考虑时,这很有意义。我尚未阅读所有答案,但似乎未指定转换过程。如果我们有一个高2位的值,而只有一个高1位的值,那么我们将有一个非零值。取非零值将导致布尔转换(如果为零,则返回false,否则为true)。然后再次求反将得到一个代表原始真理的布尔值。
乔伊·卡森

由于SO不允许我更新评论,因此我将修复我的错误。否定整数值会导致布尔值转换(如果非零,则返回false,否则返回true)。
乔伊·卡森

51

编码人员认为它将操作数转换为bool,但是由于&&的操作数已经隐式转换为bool,因此完全是多余的。


14
在没有这种技巧的情况下,Visual C ++的性能会下降。
Kirill V. Lyadvinsky 2012年

1
我想最好只是禁用警告而不是解决代码中无用的警告。
Ruslan

也许他们没有意识到。不过,这确实在宏的上下文中是很有意义的,因为您可以使用您不知道的整数数据类型。考虑括号运算符重载的对象返回代表位字段的整数值。
乔伊·卡森

12

这是一种避免写(变量!= 0)的技术-即从任何类型转换为布尔型。

这样的IMO代码在需要维护的系统中没有位置-因为它不是立即可读取的代码(因此首先出现问题)。

代码必须清晰易读-否则您将在未来留下时间债务遗留物-因为花时间了解不必要的麻烦。


8
我对把戏的定义是,并非所有人在初读时都能理解。需要弄清楚的东西是一个把戏。因为还可怕!操作员可能会超负荷...
理查德·哈里森

6
@ orlandu63:简单的类型转换是bool(expr):它做正确的事,每个人都一目了然。!!(expr)是双重否定,无意间转换为布尔值...这并不简单。
Adrien Plisson

12

是的,这是正确的,不,您不会缺少任何东西。 !!是转换为bool。有关更多讨论,请参见此问题


9

它回避了编译器警告。试试这个:

int _tmain(int argc, _TCHAR* argv[])
{
    int foo = 5;
    bool bar = foo;
    bool baz = !!foo;
    return 0;
}

'bar'行在MSVC ++上生成了一个“强制值为bool'true'或'false'(性能警告)”,但'baz'行却可以顺利完成。


1
大多数的Windows API本身不知道有关的普遍遇到的bool类型-一切都被编码为01int
Mark Ransom'1

4

是操作员!超载?
如果没有,他们可能正在这样做,以将变量转换为bool而不发出警告。这绝对不是标准的处理方式。


4

传统的C开发人员没有布尔类型,所以他们经常#define TRUE 1#define FALSE 0再使用任意的数字数据类型布尔比较。有了我们bool,当使用数字类型和布尔类型的混合进行某些类型的赋值和比较时,许多编译器将发出警告。当使用遗留代码时,这两种用法最终会冲突。

要变通解决此问题,一些开发人员使用以下布尔身份:!num_value返回bool trueif num_value == 0; false除此以外。!!num_value返回bool falseif num_value == 0; true除此以外。单个否定就足以转换num_valuebool; 但是,双重否定对于恢复布尔表达式的原始含义是必需的。

这种模式称为惯用语,即熟悉该语言的人常用的一种习惯。因此,我并没有像我那样认为它是反模式static_cast<bool>(num_value)。强制转换很可能会给出正确的结果,但是某些编译器随后会发出性能警告,因此您仍然必须解决该问题。

解决此问题的另一种方法是说(num_value != FALSE)。我也对此表示同意,但总的来说,!!num_value它的冗长程度要低得多,可能会更清晰,并且在您第二次看到它时不会感到困惑。


2

!! 用于处理没有布尔类型的原始C ++(C也没有)。


示例问题:

在内部if(condition)condition需要评估为某种类型,例如double, int, void*,等等,但是bool还不存在,因为它尚不存在。

假设存在一个类int256(256位整数),并且所有整数转换/转换都超载。

int256 x = foo();
if (x) ...

要测试x“真”还是非零,if (x)将转换x为某个整数,然后评估该整数是否int为非零。典型的重载(int) x只会返回的LSbit xif (x)当时只测试了x

但是C ++具有!运算符。重载!x通常会评估的所有位x。因此,要if (!!x)使用同相逻辑。

参考在评估`if()`语句中的条件时,旧版本的C ++是否使用了类的`int`运算符?


1

正如Marcin提到的,如果操作符重载在起作用,那可能很重要。否则,在C / C ++中,除非您要执行以下操作之一,否则不要紧:

  • 直接比较true(或在C中类似TRUE宏),这几乎总是一个坏主意。例如:

    if (api.lookup("some-string") == true) {...}

  • 您只是想将某些东西转换为严格的0/1值。在C ++中,对a的赋值bool将隐式执行此操作(对于那些可隐式转换为的事情bool)。在C语言中或者如果您正在处理一个非布尔变量,这是我见过的一个习惯用法,但是我(some_variable != 0)自己更喜欢这种变化。

我认为在更大的布尔表达式的上下文中,它只会使事情变得混乱。


1

如果变量是对象类型,则可能有一个!定义了运算符,但没有强制转换为bool(或更糟糕的是,具有不同语义的隐式强制转换为int。两次调用!运算符都会导致转换为bool,即使在奇怪的情况下也可以使用。


0

是正确的,但是在C语言中,这里没有意义-如果'if'和'&&'会在没有'!!'的情况下以相同的方式处理表达式。

我猜想在C ++中这样做的原因是'&&'可能被重载。但是,“!”也可以,因此,如果不查看and 类型的代码,就不能真正保证您得到布尔值。也许有更多C ++经验的人可以解释;也许这是一种纵深防御措施,而不是保证。variableapi.call


如果仅将其用作ifor 的操作数,则编译器将以相同的方式对待这些值&&,但是!!如果if (!!(number & mask))被替换为bit triggered = !!(number & mask); if (triggered);,则对某些编译器使用可能会有所帮助。在某些具有位类型的嵌入式编译器上,将256分配给位类型将产生零。如果没有!!,那么看似安全的转换(将if条件复制到变量然后进行分支)将不安全。
supercat

0

也许程序员在想这样的事情...

!! myAnswer是布尔值。在上下文中,它应该变成布尔值,但是我只是喜欢敲打东西以确保确定,因为从前有一个神秘的虫子咬住了我,然后砰砰地把它杀死了。


0

这可能是双重爆炸技巧的示例,请参阅《安全布尔成语》有关更多详细信息 ”。在这里,我总结了文章的第一页。

在C ++中,有许多种方法可以为类提供布尔测试。

一种明显的方法是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_;
    }
  };

我们可以测试课程,

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_;
  }

测试Testable对象的两种惯用方式是

  Testable test;
  if (!!test) 
    std::cout << "Yes, test is working!\n";
  if (!test2) {
    std::cout << "No, test2 is not working!\n";

第一个版本if (!!test)被某些人称为“ 双爆炸”把戏


1
从C ++ 11开始,可以使用它explicit operator bool来防止隐式转换为其他整数类型。
Arne Vogel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.