【问题标题】:Why pass by value and not by const reference?为什么按值传递而不是通过 const 引用传递?
【发布时间】:2015-03-24 03:52:47
【问题描述】:

因为const 引用与按值传递几乎相同,但不创建副本(据我所知)。那么是否存在需要创建变量副本的情况(因此我们需要使用按值传递)。

【问题讨论】:

标签: c++ function arguments constants


【解决方案1】:

就像很多事情一样,这是一种平衡。

我们通过 const 引用来避免复制对象。

当你传递一个 const 引用时,你传递了一个指针(引用是带有额外糖的指针,以使它们尝起来不那么苦)。当然,假设对象很容易复制。

要访问引用,编译器必须取消引用指针以获取内容[假设它不能被内联并且编译器优化了取消引用,但在这种情况下,它也会优化掉额外的副本,因此按值传递也没有损失]。

因此,如果您的副本比解引用和传递指针的总和“便宜”,那么当您通过值传递时,您就“赢了”。

当然,如果您无论如何都要制作副本,那么您最好在构造参数时制作副本,而不是稍后显式复制。

【讨论】:

    【解决方案2】:

    如果没有const_cast,则无法通过引用更改const&,但可以更改它。在代码离开编译器的“分析范围”的任何时候(可能是对不同编译单元的函数调用,或者通过函数指针无法确定编译时的值),它必须假定所引用的值可能具有改变了。

    这种成本优化。而且它可能使您更难以推理代码中可能存在的错误或怪癖:引用是非本地状态,仅在本地状态上运行且不产生副作用的函数非常容易推理关于。让你的代码易于推理是一个很大的好处:维护和修复代码比编写代码花费更多的时间,而且花在性能上的努力是可替代的(你可以把它花在重要的地方,而不是把时间浪费在任何地方的微优化上)。

    另一方面,一个值需要将该值复制到本地自动存储中,这是有成本的。

    但是,如果您的对象复制起来很便宜,并且您不希望出现上述效果,请始终按值取值,因为它使编译器更容易理解函数。

    当然,只有在复制成本低的情况下。如果复制成本很高,或者即使复制成本未知,那么 const& 的成本应该足以承担。

    上述的简短版本:取值使您和编译器更容易推理参数的状态。

    还有一个原因。如果您的对象移动起来很便宜,并且无论如何您都将存储本地副本,那么按价值获取会提高效率。如果您通过const& 获取std::string,然后制作本地副本,则可以创建一个std::string 以传递这些参数,并为本地副本创建另一个。

    如果您按值获取std::string,则只会创建一个副本(并且可能会移动)。

    举个具体的例子:

    std::string some_external_state;
    void foo( std::string const& str ) {
      some_external_state = str;
    }
    void bar( std::string str ) {
      some_external_state = std::move(str);
    }
    

    然后我们可以比较:

    int main() {
      foo("Hello world!");
      bar("Goodbye cruel world.");
    }
    

    foo 的调用会创建一个包含"Hello world!"std::string。然后将其再次复制到some_external_state。制作了 2 个副本,丢弃了 1 个字符串。

    bar 的调用直接创建std::string 参数。然后将其状态移至some_external_state。创建 1 个副本,1 个移动,丢弃 1 个(空)字符串。

    此技术还带来了某些异常安全性改进,因为任何分配都发生在 bar 之外,而 foo 可能会引发资源耗尽异常。

    这仅适用于完美转发烦人或失败、移动成本低廉、复制成本高昂以及您几乎肯定会制作参数的本地副本的情况。

    最后,有一些小类型(如int),直接复制的非优化ABI比const&参数的非优化ABI更快。这主要在编码无法或不会优化的接口时很重要,通常是微优化。

    【讨论】:

    • 谢谢,所以一般来说,如果我有一些大的东西要移动并且我不想更改它,那么我将使用 const 引用,否则如果某些东西“小”那么我只是使用 vlaue?
    • @Johnson 复制成本高且可能不可移动的东西几乎总是应该被const& 拿走。复制便宜的东西几乎总是应该按价值来衡量。介于两者之间的任何事情都需要思考。
    • 谢谢,所以有些情况下可能有便宜的东西可以复制,但我们最好使用 cont 引用权,但这些情况很小,对吗?
    • @Johnson 普遍存在的规则是通过 const 引用传递类类型,其他一切都通过值。它并不总是最优的,但任何最优的东西都取决于类型的内部知识(复制的成本有多高)或被调用的函数。这条规则适用于大多数情况,并且易于遵循。
    • @James Kanze 谢谢,你的类类型是指对象,对吗?
    【解决方案3】:

    最好的例子可能是复制和交换成语:

    C& operator=(C other)
    {
        swap(*this, other);
        return *this;
    } 
    

    使用other 值而不是const 引用可以更轻松地编写正确的赋值运算符,避免代码重复并提供强大的异常保证!

    同时传递迭代器和指针也是按值完成的,因为它使这些算法的代码更加合理,因为它们可以在本地修改它们的参数。否则像std::partition 这样的东西无论如何都必须立即复制它的输入,这既低效又看起来很傻。我们都知道避免看起来很傻的代码是第一要务:

    template<class BidirIt, class UnaryPredicate>
    BidirIt partition(BidirIt first, BidirIt last, UnaryPredicate p)
    {
        while (1) {
            while ((first != last) && p(*first)) {
                ++first;
            }
            if (first == last--) break;
            while ((first != last) && !p(*last)) {
                --last;
            }
            if (first == last) break;
            std::iter_swap(first++, last);
        }
        return first;
    }
    

    【讨论】:

      【解决方案4】:

      有些情况你不修改输入,但你仍然需要输入的内部副本,然后你不妨按值取参数。例如,假设您有一个返回向量的排序副本的函数:

      template <typename V> V sorted_copy_1(V const & v)
      {
          V v_copy = v;
          std::sort(v_copy.begin(), v_copy.end());
          return v;
      }
      

      这很好,但是如果用户有一个他们永远不需要用于任何其他目的的向量,那么您必须在此处制作一个可能不必要的强制副本。所以只需按值取参数:

      template <typename V> V sorted_copy_2(V v)
      {
          std::sort(v.begin(), v.end());
          return v;
      }
      

      现在,生成、排序和返回向量的整个过程基本上可以“就地”完成。

      成本较低的示例是消耗计数器或迭代器的算法,这些计数器或迭代器需要在算法过程中进行修改。同样,按值取值允许您直接使用函数参数,而不需要本地副本。

      【讨论】:

        【解决方案5】:
        1. 按值传递整数、浮点数和指针等基本数据类型通常更快。
        2. 您的函数可能希望在本地修改参数,而不改变传入变量的状态。
        3. C++11 引入了移动语义。要将对象移动到函数参数中,其类型不能是 const 引用。

        【讨论】:

        • 谢谢我的朋友。同样在 3. 答案中,这是否意味着由于 const 引用和 c++11 中不允许的对象而需要更新旧代码?
        • @Johnson No. 大多数旧代码在 C++11 中应该向后兼容。
        猜你喜欢
        • 2011-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-10
        • 1970-01-01
        • 2021-01-20
        相关资源
        最近更新 更多