【问题标题】:Modifying const reference argument via non-const reference argument通过非常量引用参数修改 const 引用参数
【发布时间】:2020-10-04 23:32:57
【问题描述】:

考虑以下代码:

#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&amp; aint&amp; b。因此,f 应该不会修改a,但它可以修改b,而且确实可以。
  • 但是,在 main 中,我传递了 same 变量,ab 引用了 both。由于f 修改了b,它也修改了a,它不应该这样做

此代码在没有任何警告的情况下编译并打印3。如果我们单独跟踪每个变量,看起来 const 正确性受到尊重:c 是非常量的,因此将它作为 const 引用传递为 a 以及作为非常量传递是完全可以的引用为b,在f 的主体内,我们修改b,它是非常量的,而不涉及a,它是const。然而,当c 同时用作ab 时,af 的主体内被修改,违反a 是const 的假设,即使没有明确的const_cast曾经调用过。

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

我的问题是:

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

【问题讨论】:

  • 如果使用返回值而不是引用参数会怎样? c = f(c); 会不会看起来很奇怪?
  • 我的猜测:对于f()const 的正确性是可以的。它履行了它的承诺:不修改 a 并修改 b。滥用发生在main()(对ab 使用相同的变量)。你还能做什么:签入f() 那个&amp;a != &amp;b 和/或评论一个响应。文档中的约束。 f()(如有必要)。
  • @FredLarson 会有所不同。在我的示例中,我有一个 const 变量正在修改 within 函数。在b = a+1; 之后,a 的值发生了变化。我可以将打印内容放在f 中,以使我的担忧更加明显
  • 我认为教训是返回值比输出参数更清晰。更喜欢他们。
  • 回答您的问题:是的。是的。是的,不如使用返回值。对程序员来说是的;不适合编译器。没有和没有。

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


【解决方案1】:

但是,在main 中,我传递了相同的变量,由ab 引用。由于f 修改了b,它也修改了a,它不应该这样做

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

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

是的。您不修改 const 变量。

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

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


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

【讨论】:

  • 顺便说一句,它证明了额外间接可能会造成混淆,除了有时效率低下。
【解决方案2】:

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

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

【讨论】:

  • 请注意,您所说的优化将被阻止,无论您是否真的对指针进行别名。由于编译器无法知道函数是否会使用别名参数调用,因此在为函数生成代码时,它总是必须假设最坏的情况。这是迄今为止 Fortran(完全禁止指针别名)相对于 C 和 C++ 的主要性能优势之一。
  • 除非编译器对别名进行全局分析,据我所知,许多现代编译器都可以做到(甚至在不同的编译单元之前)。
【解决方案3】:

从 API 的角度来看

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

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

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

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2012-02-25
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-21
      • 2017-07-05
      • 1970-01-01
      相关资源
      最近更新 更多