35

考虑以下代码:

#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,我传递了和引用的同一个变量。作为修改,它也修改,它应该不应该修改abfba

此代码在没有任何警告的情况下编译并打印3. 如果我们单独跟踪每个变量,看起来 const 正确性受到尊重:c是非常量的,因此将它作为constref as传递a,也可以作为非常量ref as 传递,并且在我们b的主体内传递它是完全可以的fmodify b,它是非常量的,而不接触a,它是 const 。然而,当casa和 as使用时ba在 的主体内被修改f,违反了 const 的假设a,即使没有const_cast调用显式。

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

我的问题是:

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

4 回答 4

32

但是,在 中main,我传递了由a和引用的同一个变量b。作为f修改b,它也修改a,它应该不应该修改

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

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

是的。您不修改 const 变量。

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

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


一个常量引用参数不会产生它所引用的东西,const如果它不是const开始的话。它所做的只是阻止您使用引用来修改对象。另一个指向该对象的指针或引用仍然可以改变它,只要它不是const它自己。

于 2020-06-15T14:25:52.343 回答
8

是的,代码是 const 正确的,不,这个特定的代码没有未定义的行为。您在这里写的是一个简单的引用别名案例,归结为引擎盖下的指针别名。

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

于 2020-06-15T14:32:08.133 回答
3

从 API 的角度来看

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

f 承诺不会通过引用修改任何内容,a因此尊重 const 的正确性a。此外,它还告诉用户,b另一方面,它很可能被用来修改它所针对的对象;b可能会被[in, out]记录[out]f. Ifb实际上从未用于修改它所寻址的对象,而且没有其他设计理由成为非常量引用,另一方面,这可能是实现者对 const 正确性的(较弱)违反的f

用户如何使用或误用此 API 超出了f自己直接担心的范围,尤其是在做出 API 设计选择之后。然而,任何面向用户的 API 都应该被设计成最小化(考虑到其设计限制)用户在脚上开枪的风险。例如,在这种情况下,价值语义方法int f(int const& a)int f(int copy_in_a)可用于构建面向用户的不同且更难滥用的界面。

于 2020-06-15T14:26:59.147 回答
1

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

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

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

于 2020-06-16T08:37:40.980 回答