【问题标题】:Double Negation in C++C++中的双重否定
【发布时间】:2010-09-19 21:45:42
【问题描述】:

我刚刚加入了一个代码库非常庞大的项目。

我主要处理 C++ 并且他们编写的许多代码都使用双重否定作为布尔逻辑。

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

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

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

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

【问题讨论】:

标签: c++ boolean


【解决方案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 &lt;&lt; 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++11 起,可以使用explicit operator bool 来防止隐式转换为其他整数类型。
【解决方案2】:

是的,它是正确的,不,你没有错过什么。 !! 是转换为布尔值。更多讨论请见this question

【讨论】:

    【解决方案3】:

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

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

    这种模式被称为 idiom,即熟悉该语言的人常用的东西。因此,我不认为它是一种反模式,就像static_cast&lt;bool&gt;(num_value) 一样。强制转换很可能会给出正确的结果,但是一些编译器会发出性能警告,所以你仍然必须解决这个问题。

    解决此问题的另一种方法是(num_value != FALSE)。我也同意,但总而言之,!!num_value 不那么冗长,可能更清晰,并且不会在您第二次看到它时感到困惑。

    【讨论】:

      【解决方案4】:

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


      示例问题:

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

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

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

      要测试 x 是“真”还是非零,if (x) 会将 x 转换为某个整数,然后然后评估 int 是否非零。 (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?

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

        • 我对技巧的定义并不是每个人都能在初读时理解。需要弄清楚的是一个技巧。也很可怕,因为!运算符可能被重载...
        • @orlandu63:简单的类型转换是bool(expr):它做了正确的事情,每个人都一见钟情。 !!(expr) 是双重否定,不小心转换成 bool... 这不简单。
        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

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

          'bar' 行会在 MSVC++ 上生成“强制值为 bool 'true' 或 'false'(性能警告)”,但 'baz' 行可以正常通过。

          【讨论】:

          • 最常见于不知道 bool 类型的 Windows API 本身 - 在 int 中,所有内容都被编码为 01
          【解决方案8】:

          在某些情况下,它实际上是一个非常有用的习语。使用这些宏(来自 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 位的值并且只有一位高的值,我们将有一个非零值。否定非零值会导致布尔转换(如果为零,则为假,否则为真)。然后再次否定会产生一个表示原始事实的布尔值。
          • 由于 SO 不允许我更新我的评论,我将添加一个修复我的错误。对整数值求反会导致布尔转换(如果非零则为 false,否则为 true)。
          【解决方案9】:

          也许程序员的想法是这样的......

          !!myAnswer 是布尔值。在上下文中,它应该变成布尔值,但我只是喜欢用砰砰来确定,因为从前有一个神秘的虫子咬了我,砰砰,我杀了它。

          【讨论】:

            【解决方案10】:

            正如Marcin 所提到的,运算符重载是否在起作用可能很重要。否则,在 C/C++ 中,这无关紧要,除非您正在执行以下操作之一:

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

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

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

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

            【讨论】:

              【解决方案11】:

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

              我想,在 C++ 中这样做的原因是 '&&' 可能会被重载。但是,'!' 也可以,所以它并不能真的保证你得到一个布尔值,而无需查看variableapi.call 类型的代码。也许有更多 C++ 经验的人可以解释一下;也许这是一种纵深防御措施,而不是保证。

              【讨论】:

              • 如果仅将其用作if&amp;&amp; 的操作数,编译器将以相同的方式处理这些值,但如果if (!!(number &amp; mask)) 获取,则使用!! 可能对某些编译器有所帮助替换为bit triggered = !!(number &amp; mask); if (triggered);在一些具有位类型的嵌入式编译器上,分配例如256 到位类型将产生零。如果没有!!,显然安全的转换(将if 条件复制到变量然后分支)将不安全。
              【解决方案12】:

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

              【讨论】:

              • 如果没有这个技巧,Visual C++ 在某些情况下会导致性能下降。
              • 我认为最好只禁用警告而不是解决代码中无用的警告。
              • 也许他们没有意识到。不过,这在宏的上下文中确实很有意义,您可以在其中使用您不知道的整数数据类型。考虑重载括号运算符以返回表示位域的整数值的对象。
              【解决方案13】:

              是运营商!超载?
              如果不是,他们可能这样做是为了将变量转换为布尔值而不产生警告。这绝对不是标准的做事方式。

              【讨论】:

                【解决方案14】:

                转换成bool是个技巧。

                【讨论】:

                • 我认为用 (bool) 显式转换会更清晰,为什么要使用这个棘手的 !!,因为它的输入更少?
                • 但是,它在 C++ 或现代 C 中没有意义,或者结果仅用于布尔表达式(如问题中所示)。当我们没有 bool 类型时,它很有用,有助于避免在布尔变量中存储 10 以外的值。
                • @lzprgmr:显式转换会在 MSVC 上导致 "performance warning"。使用!!!=0 解决了这个问题,在这两者中我找到了前一个清洁器(因为它适用于更多的类型)。我也同意没有理由在有问题的代码中使用任何一个。
                • @Noldorin,我认为它提高了可读性 - 如果您知道它的含义,它就简单、整洁且合乎逻辑。
                • 改善了吗?该死的地狱......给我一些你在抽烟的东西。
                猜你喜欢
                • 1970-01-01
                • 2021-06-15
                • 1970-01-01
                • 2018-01-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多