请考虑以下代码:
#include <iostream>
void f(int const& a, int& b)
{
b = a+1;
}
int main() {
int c=2;
f(c,c);
std::cout << c << std::endl;
}
f
接受两个引用参数:int const&;
和中(&P; b
。 因此,f
应该不修改a
,但是它可以修改b
,而且它确实修改了。main
中,我传递了同一个变量,它被a
和b
引用。 当f
修改b
时,它也会修改a
,而它应该不会修改a
此代码编译时没有任何警告,并打印3
。 如果我们单独跟踪每个变量,就会发现常量正确性受到尊重:c
是非常量,因此可以将其作为const
引用作为a
传递,也可以作为非常量引用作为b
传递,并且在f
的正文中,我们修改非常量的b
,而不触及常量的a
。 但是,当将c
同时用作a
和b
时,会在f
的主体内修改a
,这违反了a
是常量的假设,即使从未调用过显式的const_cast
。
我尽可能地简化了这个示例,但是人们可以很容易地想到一些不那么明显的用例(例如作用在非常量引用参数上的const
方法)。
我的问题是:
但是,在main
中,我传递了同一个变量,它被a
和b
引用。 当f
修改b
时,它也会修改a
,而它应该不会修改
当F
修改B
引用的内容时,它不会修改A
。 它修改a
所引用的内容,但这是可以的,因为b
不是const
。 只有当您试图通过使用a
修改a
引用的内容时,才会出现问题。
我们真的可以说上面的代码是常量正确的吗?
是的。 不能修改可更改的常量。
除了让读者感到困惑之外,上面的代码还能成为技术问题的根源吗? 例如未定义的行为,或者编译器执行错误的简化假设?
不,您的代码是合法的,并且将在所有符合要求的编译器上产生相同的结果。
常量引用参数不会使它所引用的东西成为常量
,如果它一开始就不是常量
。 它所做的一切都是阻止您使用引用来修改对象。 只要对象不是const
本身,指向该对象的另一个指针或引用仍然可以对其进行变异。
从API的角度来看
void f(int const& a, int& b),
f
承诺不通过a
引用修改任何内容,从而尊重a
的常量正确性。 另一方面,它还通知用户b
很可能用于修改它所寻址的对象; b
可能被记录为[in,out]
参数,或者只是[out]
参数,用于f
。 如果b
实际上从未被用于修改它所处理的对象,而且没有其他设计理由成为非常量引用,那么从另一方面来说,这可能是f
的实现者对常量正确性的(较弱的)违反。
用户如何使用或错误地使用此API超出了f
本身直接关心的范围,尤其是在其API设计选择完成之后。 然而,任何面向用户的API都应该被设计成最小化(考虑到其设计约束)用户搬起石头砸自己的脚的风险。 例如。 在这种情况下,可以使用值语义方法int f(int const&a)
或int f(int copy_in_a)
为用户构造不同的且更难滥用的接口。
是的,代码是常量正确的,不,这个特定的代码没有未定义的行为。 这里所写的是一个简单的引用别名案例,归结起来就是隐藏在引擎盖下的指针别名。
话虽如此,这样的别名通常是不可取的,因为对于程序员和编译器来说,这确实是更复杂的推理。 而且,这会阻止某些优化,特别是处理内存块。