【问题标题】:const parameter vs const reference parameterconst 参数与 const 参考参数
【发布时间】:2012-12-17 16:45:24
【问题描述】:

实施 1:

foo(const Bar x);

实施 2:

foo(const Bar & x);

如果对象不会在函数中更改,你为什么要复制它(实现 1)。

这会被编译器自动优化吗?

总结:即使对象在函数声明中被声明为const,仍然可以通过其他别名&来编辑对象。

如果您是编写库的人并且知道您的函数不这样做,或者对象足够大以证明每次操作的取消引用成本是合理的,那么 foo(const Bar & x); 是要走的路。

第 2 部分:

这会被编译器自动优化吗?

由于我们确定它们并不总是等价的,而且等价的条件也很重要,因此编译器通常很难确保它们,所以几乎可以肯定 否

【问题讨论】:

    标签: c++ optimization reference constants alias


    【解决方案1】:

    你问,

    “如果对象不会在函数内改变,你为什么要复制它(实现 1)。”

    在某些奇怪的情况下,通过引用传递的对象可能会被其他代码更改,例如

    namespace g { int x = 666; }
    
    void bar( int ) { g::x = 0; }
    
    int foo( int const& a ) { assert( a != 0 );  bar( a );  return 1000/a; }  // Oops
    
    int main() { foo( g::x ); }
    

    这在我身上从未发生过,自 1990 年代中期以来。

    所以,对于该类型的单个参数,这种别名是一个理论问题。

    如果有两个相同类型的参数,它就更有可能。例如,赋值运算符可能会传递调用它的对象。当参数按值传递时(如交换习语的最小形式),没问题,但如果不是,则通常需要避免 自赋值。

    你进一步问,

    “这会被编译器自动优化吗?”

    不,一般而言,出于上述原因

    编译器通常不能保证引用参数没有别名(不过,一个例外是调用的机器代码被内联)

    然而,另一方面,语言可以在这方面支持编译器,例如通过为程序员提供一种明确接受任何此类优化的方法,例如,一种方式可以说“通过将按值传递替换为按引用传递来优化此代码是安全的,编译器,请随心所欲”

    【讨论】:

    • 不管int foo( int const& a ) 或int foo( int const a ),事情都会变糟
    • @aiao:哦,是的。谢谢,抱歉,我没有注意到(我第一次也忘记了main)。固定。
    【解决方案2】:

    确实,在这种情况下,您通常会使用方法 2。

    通常,如果对象很小,您只会使用方法 1,因此复制一次比通过引用重复访问它更便宜(这也会产生成本)。在TC++PL 中,Stroustrup 开发了一个复数类并通过值传递它正是出于这个原因。

    【讨论】:

    • 性能是一个原因,但我想说正确性更重要。并且您的代码的正确性可能取决于正在创建的副本。
    【解决方案3】:

    在某些情况下可能会进行优化,但有很多事情可以阻止它。如果出现以下情况,编译器无法避免复制:

    • 复制构造函数或析构函数有副作用,传递的参数不是临时的。
    • 您获取x 的地址或对它的引用,然后将其传递给一些可能能够将其与原始地址进行比较的代码。
    • 对象可能会在foo 运行时更改,例如因为foo 调用了一些更改它的other 函数。我不确定这是否是您要通过说“对象不会在在函数中更改”来排除的事情,但如果不是,那么它正在发挥作用。

    如果这些事情对你的程序很重要,你会复制它:

    • 如果您想要复制的副作用,请复制一份
    • 如果您希望“您的”对象具有与用户提供的参数不同的地址,请复制一份
    • 如果您不想在函数运行期间看到对原始文件所做的更改,请复制一份

    如果您认为复制会更有效,您也可以复制它,通常认为是“小”类型,如 int。标准算法中的迭代器和谓词也是按值取值的。

    最后,如果您的代码无论如何都计划复制对象(包括通过分配给现有对象),那么一个合理的习惯用法是首先将副本作为参数。然后从您的参数中移动/交换。

    【讨论】:

      【解决方案4】:

      如果对象从其他地方改变了怎么办?

      void f(const SomeType& s);
      void g(const SomeType s);
      
      int main() {
          SomeType s;
      
          std::thread([&](){ /* s is non-const here, and we can modify it */}
      
          // we get a const reference to the object which we see as const, 
          // but others might not. So they can modify it.
          f(s);
      
          // we get a const *copy* of the object, 
          // so what anyone else might do to the original doesn't matter 
          g(s);
      }
      

      如果对象是 const,但有可变成员怎么办?然后您仍然可以修改对象,因此您是否拥有对原始对象的副本或引用非常重要。

      如果对象包含指向另一个对象的指针怎么办?如果s 是 const,则指针将是 const,但它指向的对象不受s 的 const 性影响。但是创建一个副本会(希望)给我们一个深拷贝,所以我们得到了我们自己的(const)对象和一个单独的(const)指针,指向一个单独的(non-const)对象。

      在很多情况下,const 副本与 const 引用不同。

      【讨论】:

      • “那么你仍然可以修改对象”——可能是提问者试图通过说“如果对象不会在函数中更改”来排除这种情况。但你可能是对的,提问者可能错误地认为通过 const 引用传递的对象不能更改,意思是“对象不会更改,为什么要复制它?”
      • @Steve:根据您的解释,考虑这个问题是否有意义(可能是 OP 想要澄清的问题)。
      • @Cheersandhth.-Alf:是的,这很有意义,可以澄清一下。除了因为原件改变了它的价值之外,还有其他几个潜在的理由来获取副本,因此排除这一点仍然会留下实质问题。但是,由于提问者接受了您的回答,我现在可以得出结论,这些其他原因并不令人感兴趣:-)
      • +1,因为它仍然是 0 票,确实值得点赞,因为对这个问题感兴趣的读者几乎没有其他东西可以继续评估答案了。
      猜你喜欢
      • 2021-07-12
      • 2020-07-13
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 1970-01-01
      • 2014-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多