通过引用传递再复制和通过值传递在功能上有什么区别吗?

15

这两者之间是否有功能上的区别:

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

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

4
我理解你的意思是指 Bar bar_copy(bar); - danielschemmel
3
我一直在使用这个来声明接受不完整类型(通过引用)的函数,它们的定义后来会被包含在 .cpp 文件中。 - LogicStuff
1
@wrhall 因为函数声明中的参数必须是完整类型,如果它不是通过引用或指针传递的话。 - LogicStuff
3
编译器需要知道如何构造(当然也必须分配)Bar,因为它在函数调用前被放在堆栈上。调用者需要知道对象的大小,所以它必须被完全指定。引用和指针通常作为指针传递,具有已知的大小(即sizeof(void *)),因此在进入函数之前不需要知道对象的大小。 - Mark Lakata
1
错误。它只需要在使用和定义点处完整,声明仅需要知道名称。 - Xeo
显示剩余2条评论
3个回答

22

是的,有很宝贵的区别。

void foo(Bar bar) 可以根据调用上下文复制构造移动构造bar

当一个临时对象被传递给 foo(Bar bar) 时,你的编译器可以直接在期望bar的位置构造该临时对象。感谢模板男孩。

你的函数 void foo(const Bar& bar) 总是执行复制操作。

你的函数 void foo(Bar bar) 可能 执行复制、移动或未执行任何操作。


3
当传递给函数的参数初始化值为临时对象时,你也会错过一次优化机会,因为复制或移动可以完全省略。 - template boy

14

是的,有些不同之处。最明显的一个是函数类型的改变(从而也改变了其函数指针的类型),但还存在一些不太明显的含义:

Bar 可以进行移动构造但不能进行复制构造

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

foo(Bar());

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

这意味着,只有支持移动构造的类型才能调用第二个版本,例如std::unique_ptr。事实上,手动强制复制甚至不允许该函数编译

显然,可以通过添加轻微的复杂性来缓解这种情况:

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

现在,引用和复制版本将出错,而参数作为值的版本不会报错:

void fooA(const Bar& bar)
{
    //Bar bar_copy(bar); // error: 'constexpr Bar::Bar(const Bar&)' is private
}

void fooB(Bar bar) { } // OK

既然我们声明了main是友元函数,因此以下调用是允许的(请注意,如果例如实际调用发生在Bar的静态成员函数中,则不需要友元函数):

int main()
{
    fooB(Bar()); // OK: Main is friend
}

Bar在调用点的完整性

正如评论中所观察到的,如果您希望在调用点上Bar成为不完整类型,可以使用按引用传递的版本,因为这不需要调用点能够分配Bar类型的对象。

拷贝省略的副作用

C++11 12.8/31:

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

  • [...]
  • 当未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-非限定类型的类对象时,复制/移动操作可以通过直接构造临时对象进入省略的复制/移动的目标中而被省略
  • [...]

显然,只有按值传递的版本符合此条件 - 在按引用传递后,参数绑定到一个引用上。除了观察结果有所不同之外,这也意味着失去了一种优化机会。


4

有一些不同之处。

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

即使 bar 是一个临时变量,也不能避免复制并且也不能避免移动 bar


如果您可以通过值传递来实现,那么这是否会使它严格变差呢?[即问题上有一条评论说他们使用引用传递并复制不完整类型的函数,这在按值传递版本中可能无法使用]? - wrhall
1
@wrhall:如果你按值传递,你允许对bar进行移动构造和从bar中移动。 - Jarod42
好的 - 因此在你可以传递值的情况下,你可能应该这样做,对吗?或者有不传递值的原因吗? - wrhall
1
正如注释中所述,用于“前向声明”。为了“简单起见”,在其他更频繁的情况下,最好的方法是通过const引用传递。 - Jarod42

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接