【问题标题】: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*))的指针(通常)传递,因此您无需知道您拥有的大小对象,直到您在函数内部。

标签: c++ pass-by-reference pass-by-value


【解决方案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_ptrIn 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 引用传递。
    猜你喜欢
    • 1970-01-01
    • 2013-01-21
    • 2013-02-09
    • 2013-05-03
    • 2014-08-23
    • 2015-05-23
    • 2013-12-02
    • 2023-03-07
    • 2015-01-12
    相关资源
    最近更新 更多