按引用传递然后复制和按值传递在功能上是不是不同?

Posted

技术标签:

【中文标题】按引用传递然后复制和按值传递在功能上是不是不同?【英文标题】:Is passing by reference then copying and passing by value functionally different?按引用传递然后复制和按值传递在功能上是否不同? 【发布时间】:2015-06-07 23:04:30 【问题描述】:

在功能上有区别吗:

void foo(const Bar& bar) 
  Bar bar_copy(bar);
  // Do stuff with bar_copy

void foo(Bar bar) 
  // Do stuff with bar

【问题讨论】:

我猜你的意思是Bar bar_copy(bar);? 我一直在使用它来声明采用不完整类型的函数(通过引用),其定义稍后包含在 .cpp 文件中。 @LogicStuff 你用的是上面那个?为什么你不能用底部的那个来做呢? @wrhall 因为函数声明中的参数如果不是通过引用或指针传递,则必须是完整类型。 @wrhall - 编译器需要知道如何构造(当然还有分配)Bar,因为它在函数调用之前被放入堆栈。调用者需要知道对象的大小。所以它必须完全指定。引用和指针仅作为具有已知大小(即sizeof(void*))的指针(通常)传递,因此您无需知道您拥有的大小对象,直到您在函数内部。 【参考方案1】:

是的,有一个很有价值的区别。

void foo(Bar bar) 可以复制构造移动构造bar,具体取决于调用上下文。

当一个临时值被传递给foo(Bar bar) 时,你的编译器可能能够直接在bar 预期的位置构造那个临时值。给模板男孩的帽子提示。

您的函数void foo(const Bar& bar) 始终执行复制。

您的函数void foo(Bar bar)可能执行复制或移动,也可能两者都不执行。

【讨论】:

您也错过了优化机会,因为当参数的初始化器是临时的时,复制或移动可以完全省略。【参考方案2】:

是的,有区别。虽然最明显的是函数的类型发生了变化(因此函数指针的类型也发生了变化),但也有一些不太明显的含义:

可移动构造但不可复制构造Bar

例如,假设对foo 的以下调用:

foo(Bar());

对于第一个版本,这将通过对const bar 的引用传递,然后使用复制构造函数进行复制。对于第二个版本,编译器将首先尝试移动构造函数。

这意味着,只有第二个版本可以由只能移动构造的类型调用,例如std::unique_ptr。 In fact, the manually forced copy will not even allow compilation of the function.

显然,这可以通过增加一点复杂性来缓解:

void foo(Bar&& bar) 
    // Do something with bar.
    // As it is an rvalue-reference, you need not copy it.


void foo(Bar const& bar) 
    Bar bar_copy(bar);
    foo(std::move(bar_copy));

访问说明符

有趣的是,还有另一个区别:检查访问权限的上下文。

考虑以下Bar

class Bar

    Bar(Bar const&) = default;
    Bar(Bar&&) = default;

public:
    Bar() = default;

    friend int main();
;

现在,reference-and-copy 版本会报错,而 parameter-as-value 版本不会报错:

void fooA(const Bar& bar)

    //Bar bar_copy(bar); // error: 'constexpr Bar::Bar(const Bar&)' is private


void fooB(Bar bar)   // OK

由于我们已将main 声明为朋友,因此以下调用is allowed(请注意,如果实际调用是在Barstatic 成员函数中进行的,则不需要朋友) :

int main()

    fooB(Bar()); // OK: Main is friend

呼叫现场Bar 的完整性

正如在 cmets 中所观察到的,如果您希望 Bar 在调用站点上是一个不完整的类型,可以使用传递引用版本,就像这样不需要调用站点能够分配Bar 类型的对象。

复制省略的副作用

C++11 12.8/31:

当满足某些条件时,允许实现省略类的复制/移动构造 对象,即使对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下, 该实现将省略的复制/移动操作的源和目标视为简单的两个不同 引用同一对象的方式 [...]

[...] 何时复制/移动尚未绑定到引用 (12.2) 的临时类对象 对于具有相同 cv-unqualified 类型的类对象,可以通过以下方式省略复制/移动操作 将临时对象直接构造到省略的复制/移动的目标中 [...]

显然,只有按值调用的版本符合这个标准——在通过引用传递之后,参数毕竟绑定到一个引用。 Beyond an observable difference,这也意味着失去了优化机会。

【讨论】:

【参考方案3】:

有一些不同。

void foo(const Bar& bar) 
  Bar bar_copy(bar);
  // Do stuff with bar_copy

不允许避免复制,即使bar 是临时的,也不允许移动bar

【讨论】:

如果您只能按值传递,这是否会使情况变得更糟[即对问题的评论说他们使用按引用传递,然后复制采用不完整类型的函数,他们大概不能在传值版本中使用]? @wrhall:如果按值传递,则允许 barbar 的移动构造。 对——所以在你可以按值传递的情况下,你可能应该,对吧?还是有理由不这样做? 如评论中所述,用于前向声明。对于“简单”,首选方法是在其他更频繁的情况下通过 const 引用传递。

以上是关于按引用传递然后复制和按值传递在功能上是不是不同?的主要内容,如果未能解决你的问题,请参考以下文章

理解按值传递和按引用传递

ECMAScript 中所以函数的参数都是按值传递

Python按值传递参数和按引用传递参数

C#基础按值和按引用传递参数

Java的按值传递和按引用传递解说

js按值传递和按引用传递