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

时间:2020-06-15 14:20:00

标签: c++ pass-by-reference const-correctness pass-by-const-reference

考虑以下代码:

#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& aint& b。因此,应该f不能修改a,但是它可以 修改b,并且确实可以。
  • 但是,在main中,我传递了 same 变量,该变量分别由ab引用。在f修改b的同时,它也修改了a,这本来是不应该的

此代码编译时没有任何警告,并显示3。如果我们分别跟踪每个变量,则看起来像是遵循const-correctness:c是非const,因此将其作为const ref传递为a是非常好的,并且也可以作为b的非常量引用,并且在f的正文中,我们修改了非常量的b,而没有触摸const的a 。但是,当同时使用ca时,ba的主体内被修改,这违反了{{1} }是const,即使从未调用过任何明确的f

我尽可能简化了这个示例,但是可以很容易地想到不太明显的用例(例如,对非const引用参数起作用的a方法)。

我的问题是:

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

4 个答案:

答案 0 :(得分:30)

但是,在main中,我传递了相同的变量,该变量同时被ab引用。在f修改b的同时,它也修改了a,这本来不应该

f修改b所指的内容时,它不会修改a。它修改了a所指的内容,但是没关系,因为b不是const。只有当您尝试通过使用a来修改a所指的内容时,才有问题。

我们真的可以说上面的代码是const正确的吗?

是的。您无需修改​​const变量。

除了使读者感到困惑之外,上述代码能否成为技术问题的根源?例如未定义的行为,或者编译器执行错误的简化假设?

不,您的代码是合法的,并且将在所有符合条件的编译器上产生相同的结果。


如果常量引用参数开头不是const,则常量引用参数不会使它指向const。它所做的一切都阻止您使用引用来修改对象。只要不是const本身,指向该对象的另一个指针或引用仍然可以对其进行突变。

答案 1 :(得分:7)

是的,代码是const正确的,不,此特定代码没有未定义的行为。您在此处编写的是引用别名的简单情况,它可以简化为pointer aliasing

话虽这么说,这样的别名通常是不希望的,因为对于程序员和编译器来说,这样做的理由确实更加复杂。而且,这会阻止某些优化,尤其是处理内存块。

答案 2 :(得分:3)

从API的角度来看

void f(int const& a, int& b), 

f 承诺不要通过a引用修改任何内容,因此尊重a上const的正确性。此外,它通知用户b,另一方面,很有可能用于修改它要解决的对象; b可能会被记录为[in, out]的{​​{1}}参数,或者只是[out]参数。如果f实际上从未用于修改其寻址的对象,而且没有其他设计原因可以成为非const引用,则另一方面可以说是(较弱)违反b的实现者的const正确性。

用户如何使用或滥用此API超出了f本身直接 担心的范围,尤其是在确定了API设计之后。但是,可以说,任何面向用户的API都应设计为最大程度地降低(鉴于其设计约束)用户用脚射击自己的风险。例如。在这种情况下,可以使用值语义方法fint f(int const& a)来构造面向用户的不同且难以滥用的界面。

答案 3 :(得分:1)

这确实是一个坏习惯,因为许多程序员会(错误地)假设int const& a的值在调用期间确实是恒定的。考虑

void f(int const& a, int& b)
{
  b = a+1;
  if(something) b = a+2;
}

看到b获得a+3的值会非常令人惊讶,但是如果ab指向同一个变量,就会发生这种情况。 / p>