按引用传递然后复制和按值传递在功能上是不是不同?
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(请注意,如果实际调用是在Bar
的static
成员函数中进行的,则不需要朋友) :
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:如果按值传递,则允许bar
和 bar
的移动构造。
对——所以在你可以按值传递的情况下,你可能应该,对吧?还是有理由不这样做?
如评论中所述,用于前向声明。对于“简单”,首选方法是在其他更频繁的情况下通过 const 引用传递。以上是关于按引用传递然后复制和按值传递在功能上是不是不同?的主要内容,如果未能解决你的问题,请参考以下文章