每个程序员都应该知道:
在某些情况下,为了优化程序,编译器可能会将(!p && !q)
修改为(!(p || q))
。
这两个表达式是等价的,评估第一个或第二个没有区别 但是在C ++中,可能会重载运算符,而重载的运算符可能并不总是尊重这个属性。因此,以这种方式转换代码实际上将修改代码。
编译器在!
,||
和&&
超载时是否应使用De Morgan的定律?
答案 0 :(得分:77)
请注意:
内置运营商&&和||执行短路评估(如果在评估第一个操作数后,如果结果已知,则不评估第二个操作数),但重载操作符的行为类似于常规函数调用,始终评估两个操作数强>
... 因为操作员的短路特性和&和运算符||不适用于重载,并且因为具有布尔语义的类型不常见,只有两个标准库类重载这些运算符......
来源:http://en.cppreference.com/w/cpp/language/operator_logical (强调我的)
那:
如果存在具有相同名称的用户书写候选人 和参数类型作为内置候选运算符函数,隐藏内置运算符函数和 不包括在候选函数集中。
来源:n4431 13.6内置运算符[over.built](强调我的)
总结一下:重载运算符的行为类似于常规的用户编写函数。
不,编译器不会用另一个用户编写的函数调用替换用户编写函数的调用。 否则可能会违反"as if"规则。
答案 1 :(得分:17)
我认为你已回答了自己的问题:不,编译器不能这样做。不仅操作员可能过载,有些甚至无法定义。例如,您可以定义operator &&
和operator !
,并且根本不会定义operator ||
。
请注意,编译器无法遵循许多其他法律。例如,它无法将p||q
更改为q||p
,也可以将x+y
更改为y+x
。
(以上所有内容都适用于重载运算符,因为这是问题所要求的。)
答案 2 :(得分:9)
不,在这种情况下,转换将无效。将!p && !q
转换为!(p || q)
的权限是由as-if规则隐含的。 as-if规则允许任何转换,粗略地说,正确的程序无法观察到。当使用重载运算符并检测到转换时,这自动意味着不再允许转换。
答案 3 :(得分:5)
重载的运算符本身只是函数调用的语法糖;编译器本身不允许对可能存在或不存在此类调用的属性做出任何假设。利用某些特定运算符的属性的优化(例如,De Morgan用于布尔运算符,sums的交换性,sum / product的分布性,在适当乘法中的积分除法的转换,......)只能在“真实运算符”时使用使用。
请注意,标准库的某些部分可能会将某些特定的语义含义与重载的运算符相关联 - 例如,std::sort
默认情况下需要operator<
符合a元素之间严格的弱排序 - 但这当然列在每个算法/容器的先决条件中。
(顺便说一句,无论如何都应该避免重载&&
和||
,因为它们在超载时会失去其短路特性,因此它们的行为会变得令人惊讶并因此具有潜在的危险性。
答案 4 :(得分:4)
您在询问编译器是否可以随意重写您的程序以执行您未编写的操作。
答案是:当然不是!
真的那么简单。
答案 5 :(得分:3)
不直接。
如果p和q是表达式,使得p没有重载运算符,则短路评估有效:仅当p为假时才会计算表达式q。
如果p是非基本类型,则没有短路评估,过载功能可能是任何东西 - 甚至与传统用法无关。
编译器将以自己的方式进行优化。也许它可能会导致de Morgan身份,但不会导致条件替换。
答案 6 :(得分:3)
DeMorgan的法律适用于这些运营商的语义。重载适用于这些运算符的语法。无法保证重载运算符实现DeMorgan法律所需的语义。
答案 7 :(得分:1)
但是在C ++中,可能会重载运算符,而重载的运算符可能并不总是尊重这个属性。
重载运算符不再是运算符,而是函数调用。
class Boolean
{
bool value;
..
Boolean operator||(const Boolean& b)
{
Boolean c;
c.value = this->value || b.value;
return c;
}
Boolean logical_or(const Boolean& b)
{
Boolean c;
c.value = this->value || b.value;
return c;
}
}
所以这行代码
Boolean a (true);
Boolean b (false);
Boolean c = a || b;
等同于此
Boolean c = a.logical_or(b);