C + + 中的双重否定

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

我主要处理的是 C + + ,他们编写的很多代码都使用了双重否定来表示布尔逻辑。

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

我知道这些家伙是聪明的程序员,很明显他们这么做不是偶然的。

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

是这样吗,还是我遗漏了什么?

39417 次浏览

这是一个把戏,转换为布尔。

接线员超载了吗?
如果没有,他们可能这样做是为了在没有产生警告的情况下将变量转换成一个 bool。这绝对不是一种标准的做事方式。

是的,它是正确的,不,你没有错过什么。!!是向 bool 的转换。有关更多讨论,请参见 这个问题

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

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

代码必须清晰易读——否则就会给将来留下一笔时间债务——因为理解一些不必要的复杂的东西需要时间。

程序员认为它会将操作数转换为 bool,但是因为 & & 的操作数已经隐式地转换为 bool,所以它完全是多余的。

它是正确的,但是在 C 中,在这里没有意义——‘ if’和‘ & &’将以同样的方式处理表达式,而不使用‘ ! !'.

我想,在 C + + 中这样做的原因是‘ & &’可能会过载。但是,那么可能,所以它不能保证 真的得到一个 bool,不需要查看 variableapi.call类型的代码。也许有更多 C + + 经验的人可以解释; 也许这意味着一种深度防御措施,而不是一种保证。

正如 ABc0所提到的,运算符重载是否参与其中可能很重要。否则,在 C/C + + 中,除非你正在做以下事情之一,否则这些都不重要:

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

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

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

我认为在一个更大的布尔表达式的上下文中,它只是把东西弄得乱七八糟。

也许程序员是这么想的。

!!我的答案是布尔型的。在上下文中,它应该是布尔型的,但是我只是喜欢敲打东西来确保,因为从前有一个神秘的虫子咬了我,然后砰砰,我杀了它。

实际上,在某些情况下,这是一个非常有用的习语。以这些宏为例(来自 Linux 内核的例子)。对于海湾合作委员会,它们的实施方式如下:

#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 + + 中都可以做的事情。

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

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

在 MSVC + + 上,‘ bar’行生成一个“强制值 bool‘ true’或‘ false’(性能警告)”,但是‘ baz’行可以通过。

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

!! was used to cope with original C++ which did not have a boolean type (as neither did C).


例题:

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

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

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

为了测试 x是“真”还是非零,if (x)x转换成某个整数,那么评估 int是否为非零。(int) x的典型过载将只返回 x的 LSbit。当时 if (x)只测试 x的 LSbit。

但是 C + + 有 !操作符。重载的 !x通常会计算 x的所有位。因此,回到非倒置逻辑 if (!!x)的使用。

裁判 旧版本的 C + + 在评估‘ if ()’语句中的条件时是否使用了类的‘ int’运算符?

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

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

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

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

这可能是 双爆炸魔术的一个例子(有关更多细节,请参见 安全布尔成语)。这里我总结一下文章的第一页。

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