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

时间:2021-02-19 16:33:05

Is there a functional difference between:

两者之间是否存在功能差异:

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

and

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

3 个解决方案

#1


23  

Yes, there is a valuable difference.

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

void foo(Bar bar) can copy-construct or move-construct bar, depending on the calling context.

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

And when a temporary is passed to foo(Bar bar), your compiler may be able to construct that temporary directly where bar is expected to be. Hat tip to template boy.

当临时传递给foo(条形栏)时,您的编译器可能能够直接构建那个临时条形的临时区域。帽子提示模板男孩。

Your function void foo(const Bar& bar) always performs a copy.

你的函数void foo(const Bar&bar)总是执行一个副本。

Your function void foo(Bar bar) may perform a copy or a move or possibly neither.

你的函数void foo(条形图条)可以执行复制或移动,也可能两者都不执行。

#2


12  

Yes, there are differences. While the most obvious one is that the type of the function changes (and thus the type of its function pointer), there are also some less obvious implications:

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

Move-constructible but not copy-constructible Bar

For example, assume the following call to foo:

例如,假设以下调用foo:

foo(Bar());

For the first version, this will be passed by a reference to const bar and then copied using the copy constructor. For the second version, the compiler will attempt the move-constructor first.

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

This means, that the only the second version can be called by types that are move-constructible only, like std::unique_ptr. In fact, the manually forced copy will not even allow compilation of the function.

这意味着,唯一的第二个版本只能由可移动构造的类型调用,如std :: unique_ptr。实际上,手动强制复制甚至不允许编译该功能。

Obviously, this can be mitigated by adding a slight complication:

显然,这可以通过添加一个轻微的复杂性来减轻:

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

Access specifiers

Interestingly, there is another difference: The context in which access permissions are checked.

有趣的是,还有另一个不同之处:检查访问权限的上下文。

Consider the following Bar:

考虑以下栏:

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

public:
    Bar() = default;

    friend int main();
};

Now, the reference-and-copy version will error out, while the parameter-as-value version will not complain:

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

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

void fooB(Bar bar) { } // OK

Since we have declared main to be a friend, the following call is allowed (note that the friend would not be necessary if, e.g. the actual call was made in a static member function of Bar):

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

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

Completeness of Bar at the call site

As has been observed in the comments, if you wish Bar to be an incomplete type at the call site, it is possible to use the pass-by-reference version, as this does not require the call site to be able to allocate an object of type Bar.

正如在评论中观察到的那样,如果您希望Bar在呼叫站点上是一个不完整的类型,则可以使用pass-by-reference版本,因为这不需要呼叫站点能够分配对象Bar的类型。

Copy Elision Side-Effects

C++11 12.8/31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object [...]

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

  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的复制/移动

  • [...]

Obviously, only the call-by-value version meets this criterium - after passing by reference, the parameter is bound to a reference after all. Beyond an observable difference, this also means that an optimization oppurtunity is lost.

显然,只有按值调用的版本符合此标准 - 在通过引用传递之后,参数毕竟被绑定到引用。除了可观察到的差异之外,这也意味着失去了优化机会。

#3


4  

There are some differences.

有一些差异。

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

doesn't allow to avoid the copy even if bar was a temporary and avoid also the move of bar.

即使酒吧是临时的,也不允许避免复制,也避免了酒吧的移动。

#1


23  

Yes, there is a valuable difference.

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

void foo(Bar bar) can copy-construct or move-construct bar, depending on the calling context.

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

And when a temporary is passed to foo(Bar bar), your compiler may be able to construct that temporary directly where bar is expected to be. Hat tip to template boy.

当临时传递给foo(条形栏)时,您的编译器可能能够直接构建那个临时条形的临时区域。帽子提示模板男孩。

Your function void foo(const Bar& bar) always performs a copy.

你的函数void foo(const Bar&bar)总是执行一个副本。

Your function void foo(Bar bar) may perform a copy or a move or possibly neither.

你的函数void foo(条形图条)可以执行复制或移动,也可能两者都不执行。

#2


12  

Yes, there are differences. While the most obvious one is that the type of the function changes (and thus the type of its function pointer), there are also some less obvious implications:

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

Move-constructible but not copy-constructible Bar

For example, assume the following call to foo:

例如,假设以下调用foo:

foo(Bar());

For the first version, this will be passed by a reference to const bar and then copied using the copy constructor. For the second version, the compiler will attempt the move-constructor first.

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

This means, that the only the second version can be called by types that are move-constructible only, like std::unique_ptr. In fact, the manually forced copy will not even allow compilation of the function.

这意味着,唯一的第二个版本只能由可移动构造的类型调用,如std :: unique_ptr。实际上,手动强制复制甚至不允许编译该功能。

Obviously, this can be mitigated by adding a slight complication:

显然,这可以通过添加一个轻微的复杂性来减轻:

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

Access specifiers

Interestingly, there is another difference: The context in which access permissions are checked.

有趣的是,还有另一个不同之处:检查访问权限的上下文。

Consider the following Bar:

考虑以下栏:

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

public:
    Bar() = default;

    friend int main();
};

Now, the reference-and-copy version will error out, while the parameter-as-value version will not complain:

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

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

void fooB(Bar bar) { } // OK

Since we have declared main to be a friend, the following call is allowed (note that the friend would not be necessary if, e.g. the actual call was made in a static member function of Bar):

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

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

Completeness of Bar at the call site

As has been observed in the comments, if you wish Bar to be an incomplete type at the call site, it is possible to use the pass-by-reference version, as this does not require the call site to be able to allocate an object of type Bar.

正如在评论中观察到的那样,如果您希望Bar在呼叫站点上是一个不完整的类型,则可以使用pass-by-reference版本,因为这不需要呼叫站点能够分配对象Bar的类型。

Copy Elision Side-Effects

C++11 12.8/31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object [...]

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

  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的复制/移动

  • [...]

Obviously, only the call-by-value version meets this criterium - after passing by reference, the parameter is bound to a reference after all. Beyond an observable difference, this also means that an optimization oppurtunity is lost.

显然,只有按值调用的版本符合此标准 - 在通过引用传递之后,参数毕竟被绑定到引用。除了可观察到的差异之外,这也意味着失去了优化机会。

#3


4  

There are some differences.

有一些差异。

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

doesn't allow to avoid the copy even if bar was a temporary and avoid also the move of bar.

即使酒吧是临时的,也不允许避免复制,也避免了酒吧的移动。