提问者:小点点

通过非常量引用参数修改常量引用参数


请考虑以下代码:

#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中,我传递了同一个变量,它被ab引用。 当f修改b时,它也会修改a,而它应该不会修改a

此代码编译时没有任何警告,并打印3。 如果我们单独跟踪每个变量,就会发现常量正确性受到尊重:c是非常量,因此可以将其作为const引用作为a传递,也可以作为非常量引用作为b传递,并且在f的正文中,我们修改非常量的b,而不触及常量的a。 但是,当将c同时用作ab时,会在f的主体内修改a,这违反了a是常量的假设,即使从未调用过显式的const_cast

我尽可能地简化了这个示例,但是人们可以很容易地想到一些不那么明显的用例(例如作用在非常量引用参数上的const方法)。

我的问题是:

    我们真的可以说上面的代码是常量正确的吗?
  • 上面的用例是已知的模式吗? 这被认为是不良做法吗?
  • 除了让读者感到困惑之外,上面的代码还能成为技术问题的根源吗? 例如未定义的行为或编译器执行错误的简化假设?

共3个答案

匿名用户

但是,在main中,我传递了同一个变量,它被ab引用。 当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)为用户构造不同的且更难滥用的接口。

匿名用户

是的,代码是常量正确的,不,这个特定的代码没有未定义的行为。 这里所写的是一个简单的引用别名案例,归结起来就是隐藏在引擎盖下的指针别名。

话虽如此,这样的别名通常是不可取的,因为对于程序员和编译器来说,这确实是更复杂的推理。 而且,这会阻止某些优化,特别是处理内存块。