C++中的二重否定
Double Negation in C++
我刚刚发现一个项目有一个非常庞大的代码库。
我主要处理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"&;'如果没有"!!",将以同样的方式对待该表达式。
我想,在C++中这样做的原因是‘&;'可能过载。但是,"!",因此,在不查看variable
和api.call
类型的代码的情况下,它并不能真正保证您得到一个bool。也许有更多C++经验的人可以解释;也许这是一种深度防御措施,而不是一种保证。
也许程序员在想这样的事情。。。
myAnswer是布尔值。在上下文中,它应该变成布尔值,但我只是喜欢砰砰作响来确保,因为从前有一个神秘的bug咬了我,砰砰作响,我把它杀死了。
- 如何将stdout重定向到stderr
- 将二维矢量传递给类
- 排序后如何将二维数组重置为原始形式
- 最小/最大整数和二重
- 在二重数组中递归查找负数
- 计算二重分子和分母的最准确方法
- 为什么这个正二重的腹肌等于零
- 对二重向量的向量进行排序
- 在C++中检查二重是否等于负无穷大的最佳方法
- 两个区间[a,b]之间的二重随机数生成
- 将boost用于长二重的(伪)随机数生成器
- 随机二重总是无限的
- 我怎样才能在c++中计算二重的阶乘
- 与变量长度无关的二重乘法
- c++求两个二重的标量乘积
- c++将向量归一化为二重
- 我怎样才能得到二重的精确值
- 重置矢量的二维阵列
- 利用GDAL/OGR库实现二维地理参考栅格数据阵列的栅格数据重投影
- C++中的二重否定