C++中的二重否定

Double Negation in C++

本文关键字:二重 C++      更新时间:2023-10-16

我刚刚发现一个项目有一个非常庞大的代码库。

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

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

我知道这些人都是聪明的程序员,很明显他们这样做不是偶然的。

我不是经验丰富的C++专家,我唯一的猜测是他们为什么这么做,是他们想绝对肯定被评估的值是实际的布尔表示。所以他们否定它,然后再次否定它,使它回到实际的布尔值。

这是正确的吗,还是我遗漏了什么?

转换为bool是一个技巧。

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

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

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

他们可能会通过与0进行比较来做同样的事情,但在我看来,做双重否定实际上更简单,因为这是C所拥有的最接近于bool的类型。

这个代码也可以在C++中使用。。。这是一个最低公分母的东西。如果可能的话,做在C和C++中都有效的事情。

编码器认为它会将操作数转换为bool,但因为&已经隐式地转换为bool,这是完全多余的。

是的,它是正确的,不,你没有遗漏什么。!!是布尔的转换。有关更多讨论,请参阅此问题。

这是一种避免写入(variable!=0)的技术,即从任何类型转换为bool。

像这样的国际海事组织代码在需要维护的系统中没有立足之地,因为它不是即时可读的代码(因此首先存在问题)。

代码必须清晰易读,否则会给未来留下时间债务遗产,因为理解一些不必要的复杂内容需要时间。

它会附带编译器警告。试试这个:

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

"bar"行在MSVC++上生成一个"强制值为bool"true"或"false"(性能警告)",但"baz"行偷偷通过fine。

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

为了解决这个问题,一些开发人员使用以下布尔标识:如果num_value == 0,则!num_value返回bool true;否则为false。如果CCD_ 16,则CCD_ 14返回CCD_;否则为CCD_ 17。单个否定足以将num_value转换为bool;然而,双重否定对于恢复布尔表达式的原始意义是必要的。

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

解决这一问题的另一种方法是(num_value != FALSE)。我也同意这一点,但总的来说,!!num_value远没有那么冗长,可能更清晰,第二次看到它时也不会感到困惑。

是运算符!过载
如果没有,他们这样做可能是为了在不产生警告的情况下将变量转换为bool。这绝对不是一种标准的做事方式。

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


示例问题:

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

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

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

为了测试x是否为"true"或非零,if (x)x转换为某个整数,然后评估该int是否为非零。CCD_ 33的典型过载将仅返回CCD_。CCD_ 35当时仅测试CCD_ 36的LSbit。

但是C++有!运算符。过载的CCD_ 38通常会评估CCD_ 39的所有比特。因此,为了回到非反相逻辑,使用了CCD_ 40。

Ref旧版本的C++在计算"if()"语句中的条件时是否使用了类的"int"运算符?

正如Marcin所提到的,如果运算符重载正在发挥作用,这可能很重要。否则,在C/C++中,这无关紧要,除非你正在做以下事情之一:

  • 直接与true(或者在C中类似于TRUE宏的东西)进行比较,这几乎总是一个坏主意。例如:

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

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

我认为在一个更大的布尔表达式的上下文中,它只是把事情搞得一团糟。

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

这可能是双爆炸技巧的一个例子(有关更多详细信息,请参阅安全布尔习语)。在这里我总结一下这篇文章的第一页。

在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)就是一些人所说的双爆炸技巧

这是正确的,但在C中,这里没有意义——"if"answers"&amp;'如果没有"!!",将以同样的方式对待该表达式。

我想,在C++中这样做的原因是‘&amp;'可能过载。但是,"!",因此,在不查看variableapi.call类型的代码的情况下,它并不能真正保证您得到一个bool。也许有更多C++经验的人可以解释;也许这是一种深度防御措施,而不是一种保证。

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

myAnswer是布尔值。在上下文中,它应该变成布尔值,但我只是喜欢砰砰作响来确保,因为从前有一个神秘的bug咬了我,砰砰作响,我把它杀死了。