C++中的双重否定

147

我刚参加了一个非常庞大的项目。

我主要处理C++,他们写的很多代码在布尔逻辑中使用双重否定。

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

我知道这些家伙是聪明的程序员,很明显他们不是无意中这样做的。

我并不是经验丰富的C++专家,我唯一猜测他们这样做的原因是希望确认正在评估的值为实际的布尔表示。所以他们对它取反,然后再次取反以使其返回其实际的布尔值。

这个理解正确吗,还是我漏掉了什么?


5
点击这里,已经提问过了。在C++中,使用!!将变量转换为bool类型是安全的吗? - Özgür
这个主题已经在这里讨论过了。 - Dima
1
可能是在C++中将!!用作安全转换为bool的方法吗?的重复问题。 - EdChum
14个回答

139

这是一个将值转换为布尔类型的技巧。


24
我认为使用(bool)来明确转换类型会更清晰,为什么要使用这个棘手的!!呢?是因为打字更少吗? - Baiyan Huang
33
然而,在C++或现代C中,或者当结果仅在布尔表达式中使用时(如问题所示),这是无意义的。在我们没有“bool”类型的时候,它是有用的,可以帮助避免在布尔变量中存储除“1”和“0”以外的值。 - Mike Seymour
9
@lzprgmr:在MSVC上,显式转换会引起性能警告。使用 !!!=0 就可以解决这个问题,其中我认为前者更加简洁(因为它可以适用于更多的数据类型)。此外,我同意在所涉及的代码中不需要使用任何一种方式。 - Yakov Galka
8
@Noldorin,我认为它提高了可读性——如果你知道它的意思,它就简洁、清晰和逻辑性强。 - jwg
32
改善?该死的...给我点你在吸什么的东西。 - Noldorin
显示剩余2条评论

78

在某些情况下,它实际上是一个非常有用的习语。 以这些宏为例(来自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个位高,并且与只有一个位高的值相与,我们将得到一个非零值。否定一个非零值会导致布尔转换(如果为零,则为false,否则为true)。然后再次否定会产生一个表示原始真相的布尔值。 - Joey Carson
由于SO不允许我更新我的评论,因此我将修复我的错误。否定一个整数值会导致布尔转换(如果它是非零的,则为false,否则为true)。 - Joey Carson

53

程序员认为这将把操作数转换为布尔值,但由于 && 的操作数已经隐式转换为布尔值,因此这是完全冗余的。


15
在某些情况下,如果不使用这个技巧,Visual C++ 会给出性能警告。 - Kirill V. Lyadvinsky
1
我认为最好的方法是禁用警告,而不是在代码中绕过无用的警告。 - Ruslan
也许他们没有意识到。但是在宏的上下文中,这是完全有道理的,因为你可能正在使用你不知道的整数数据类型。考虑重载括号运算符以返回表示位字段的整数值的对象。 - Joey Carson

14

是的,这是正确的,你没有漏掉什么。 !! 是一个转换为布尔型的操作。参见这个问题以了解更多讨论。


13

这是一种避免编写(variable != 0)的技巧 - 即将其从任何类型转换为bool。

在需要维护的系统中,我认为这样的代码没有存在的必要 - 因为它不是立即可读的代码(这也是首次提出问题的原因)。

代码必须易于阅读 - 否则你会留下时间债务的遗产 - 因为理解那些不必要复杂的东西需要时间。


8
“把一个复杂的东西说清楚是一件困难的事情”,这句话中的“trick”指的是那些不是每个人在第一次阅读时都能理解的东西。需要解决的问题也可以被称作“trick”。同样,使用“!”操作符可能会很糟糕,因为它可能被重载。 - Richard Harrison
6
简单的类型转换是使用bool(expr):它能够正确地运作并且每个人都能在第一眼看到时理解其意图。而!!(expr)则是双重否定,会偶然地转换为bool型……这并不简单。 - Adrien Plisson

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类型 - 所有内容都被编码为int中的01 - Mark Ransom

6

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

为了解决这个问题,一些开发人员使用以下布尔恒等式:!num_value如果num_value == 0则返回bool true;否则返回false!!num_value 如果num_value == 0则返回bool false;否则返回true。单个否定足以将num_value转换为bool;但是,双重否定是必要的,以恢复布尔表达式的原始含义。

这种模式被称为惯用语,即熟悉该语言的人常用的东西。因此,我不认为它是反模式,就像我对static_cast<bool>(num_value)一样。转换可能确实给出正确的结果,但是一些编译器会发出性能警告,因此您仍然需要解决这个问题。

另一种解决方法是说(num_value != FALSE)。我也可以接受这个,但总的来说,!!num_value要更简洁,可能更清晰,并且第二次看到它时不会混淆。


4

运算符!被重载了吗?
如果没有,他们可能是这样做的,将变量转换为布尔值而不产生警告。这绝对不是标准的做法。


2

!!被用来处理最初的C++,因为它没有布尔类型(C也是如此)。


示例问题:

if(condition)中,condition需要评估一些类型,例如double、int、void*等,但不能是bool,因为它还不存在。

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

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

为了测试变量 x 是否为 "true" 或非零值,if (x) 语句会将 x 转换为一个整数,然后判断该整数是否为非零。通常情况下,重载的 (int) x 只返回 x 的 LSbits(最低有效位)。 因此,if (x) 只检查 x 的 LSbits。
但是 C++ 中有一个 ! 运算符。重载的 !x 通常会评估 x 的所有位。因此,为了恢复非反转逻辑,可以使用 if (!!x)
参考:Did older versions of C++ use the `int` operator of a class when evaluating the condition in an `if()` statement?

1
这可能是“双重否定技巧”的一个例子(有关更多详细信息,请参见The Safe Bool Idiom)。这里我总结了文章的第一页。
在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";
}

然而,operator 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 (!test) {
    std::cout << "No, test is not working!\n";
}

第一个版本 if (!!test) 是一些人所谓的双重非逻辑运算符技巧


2
自从C++11以来,我们可以使用“explicit operator bool”来防止将类型隐式转换为其他整数类型。 - Arne Vogel

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