为什么传递值而不是const引用?

时间:2015-01-25 15:42:23

标签: c++ function arguments const

由于const引用与传递值几乎相同,但没有创建副本(据我所知)。那么是否需要创建变量的副本(因此我们需要使用pass by value)。

5 个答案:

答案 0 :(得分:13)

在某些情况下,您不会修改输入,但仍需要输入的内部副本,然后您也可以按值获取参数。例如,假设您有一个返回向量的已排序副本的函数:

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;
}

现在,生成,排序和返回向量的整个过程基本上可以在原地进行,并且就地完成#34;。

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

答案 1 :(得分:5)

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

答案 2 :(得分:5)

像许多事情一样,它是一种平衡。

我们通过const引用来避免制作对象的副本。

当你传递一个const引用时,你传递一个指针(引用是带有额外糖的指针,使它们味道更少苦)。当然,假设对象很容易复制。

要访问引用,编译器必须取消引用指针以获取内容[假设它不能内联并且编译器优化了取消引用,但在这种情况下,它还将优化掉额外的副本,所以没有任何损失可以通过值]。

所以,如果你的副本更便宜&#34;而不是取消引用和传递指针的总和,那么你赢了&#34;当你通过价值时。

当然,如果你打算复制一个副本,那么你也可以在构造论证时制作副本,而不是稍后明确复制。

答案 3 :(得分: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 :(得分:1)

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

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

另一方面,值需要将值复制到本地自动存储中,这会产生成本。

但是如果您的对象复制起来很便宜,并且您不希望发生上述效果,那么请始终按值进行操作,因为它会使编译器更容易理解该函数。

当然只有价值便宜才能复制。如果复制费用昂贵,或者即使复制费用未知,则该费用应该足够std::string

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

还有另一个原因。如果您的对象移动成本低廉,并且您无论如何都要存储本地副本,那么按价值计算可以提高效率。如果您使用const& std::string,然后制作本地副本,则可以创建一个std::string以传递thes参数,另一个创建用于本地副本。

如果按值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

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

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

此技术也会导致某些异常安全改进,因为任何分配都发生在bar之外,而foo可能会导致资源耗尽异常。

这仅适用于完美转发会令人讨厌或失败,当移动被认为便宜,复制可能很昂贵,以及当您知道您几乎肯定要制作参数的本地副本时。

最后,有一些小型类型(如int),其中直接拷贝的非优化ABI比const&参数的非优化ABI快。这在编码不能优化或不优化的接口时非常重要,通常是微优化。