[英]Why is there a copy when creating an rvalue and passing it to a function?
To better understand copy elision I wrote a test app that did a simple action in copy and move constructors/assignment operators and counted the times it was copied or moved.为了更好地理解复制省略,我编写了一个测试应用程序,它在复制和移动构造函数/赋值运算符中执行了一个简单的操作,并计算了它被复制或移动的次数。 I noticed however that there was a copy when I created an rvalue and passed it directly rather than creating a lvalue then passing it in.
然而,我注意到当我创建一个右值并直接传递它而不是创建一个左值然后将它传入时有一个副本。
I'm trying to understand if this is compiler defined or part of the language specification?我想了解这是编译器定义的还是语言规范的一部分? Additionally I'm trying to understand why it wouldn't be omitted as it seems like it should just be a construction rather than a copy?
此外,我试图理解为什么它不会被省略,因为它似乎应该只是一个结构而不是副本?
godbolt of what I tested 我测试的神马
struct Bar {
Bar() {}
Bar(const Bar& a)
: cp_count{a.cp_count + 1}, mv_count{a.mv_count} {}
Bar& operator=(const Bar& a) {
cp_count = a.cp_count + 1;
mv_count = a.mv_count;
return *this;
}
Bar(Bar&& a)
: cp_count{a.cp_count}, mv_count{a.mv_count + 1} {}
Bar& operator=(Bar&& a) {
cp_count = a.cp_count;
mv_count = a.mv_count + 1;
return *this;
}
int cp_count = 0;
int mv_count = 0;
};
struct Foo {
Bar bar;
std::function<void(Bar)> setter;
std::function<void(Bar)> setter2;
Foo() {
setter = [this](Bar a) {
bar = a;
};
setter2 = [this](Bar a) {
bar = std::move(a);
};
}
};
int main() {
std::cout << std::endl << "base line" << std::endl;
{
Foo foo;
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
std::cout << std::endl << "in-place then copy" << std::endl;
{
Foo foo;
foo.setter(Bar{});
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
std::cout << std::endl << "copy then copy" << std::endl;
{
Foo foo;
Bar bar{};
foo.setter(bar);
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
std::cout << std::endl << "move then copy" << std::endl;
{
Foo foo;
Bar bar{};
foo.setter(std::move(bar));
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
std::cout << std::endl << "in-place then move" << std::endl;
{
Foo foo;
foo.setter2(Bar{});
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
std::cout << std::endl << "copy then move" << std::endl;
{
Foo foo;
Bar bar{};
foo.setter2(bar);
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
std::cout << std::endl << "move then move" << std::endl;
{
Foo foo;
Bar bar{};
foo.setter2(std::move(bar));
std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
}
}
The output I received from my testing我从测试中收到的输出
base line
mv_count = 0 cp_count = 0
in-place then copy
mv_count = 1 cp_count = 1
copy then copy
mv_count = 1 cp_count = 2
move then copy
mv_count = 2 cp_count = 1
in-place then move
mv_count = 2 cp_count = 0
copy then move
mv_count = 2 cp_count = 1
move then move
mv_count = 3 cp_count = 0
The additional moves seen in the output are caused by std::function<void(Bar)>
.在输出中看到的额外移动是由
std::function<void(Bar)>
。 Change the definition of Foo
to将
Foo
的定义更改为
struct Foo
{
Bar bar;
void setter(Bar a) { bar = a; }
void setter2(Bar a) { bar = std::move(a); }
};
and the output becomes并且输出变成
base line
mv_count = 0 cp_count = 0
in-place then copy
mv_count = 0 cp_count = 1
copy then copy
mv_count = 0 cp_count = 2
move then copy
mv_count = 1 cp_count = 1
in-place then move
mv_count = 1 cp_count = 0
copy then move
mv_count = 1 cp_count = 1
move then move
mv_count = 2 cp_count = 0
which is what you should expect.这是你应该期待的。 For example, for in-place then copy , the parameter
a
of setter
is initialized from the argument prvalue Bar{}
(guaranteed copy elision in C++17), then is copy-assigned to bar
(one invocation of Bar
's copy-assignment operator);例如,对于in-place then copy ,
setter
的参数a
从参数 prvalue Bar{}
初始化(在 C++17 中保证复制省略),然后被复制分配给bar
(对Bar
的复制的一次调用-赋值运算符); overall, no moves, one copy.总体来说,没有动作,一份副本。
When setter
is std::function<void(Bar)>
, setter(x)
calls std::function
's operator()(Bar arg)
, which wraps your lambda closure object's operator()(Bar a)
.当
setter
是std::function<void(Bar)>
, setter(x)
调用std::function
的operator()(Bar arg)
,它包装了你的 lambda 闭包对象的operator()(Bar a)
。 It basically passes std::forward<Bar>(arg)
to your operator()
, adding one move construction (of a
from std::forward<Bar>(arg)
) to all cases, which explains the results you were seeing.它基本上通行证
std::forward<Bar>(arg)
到你的operator()
中添加一个移动建设a
从std::forward<Bar>(arg)
所有的情况,这说明你看到的结果。
The additional move construction cannot be elided, since不能省略额外的移动结构,因为
std::forward<Bar>(arg)
is an xvalue, not a prvalue (no guaranteed copy elision); std::forward<Bar>(arg)
是一个 xvalue,而不是一个纯右值(不保证复制省略);Bar
's move constructor has side effects; Bar
的移动构造函数有副作用;return
, not a throw
, not an exception-declaration ).return
,不是throw
,不是exception-declaration )。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.