[英]Two-step copy elision to capture rvalue in constructor call as instance variable
I am trying to get an rvalue
instance of this class: 我正在尝试获取此类的rvalue
实例:
#include <iostream>
#define msg(x) std::cout << x " constructor\n"
struct X {
int i;
X(int i) : i(i) {msg("X");}
X(const X& x) : i(x.i) {std::cout << "X copy\n";}
X(X&& x) {std::swap(i, x.i); std::cout << "X move\n";}
};
into instance variable x
of this class: 到此类的实例变量x
中:
struct A {
X x;
A(X x) : x(x) {msg("A");}
};
like so: 像这样:
int main() {
A a(X(1));
std::cout << a.x.i << "\n\n";
}
without any copies or moves being made . 没有任何复制或移动 。
According to these references, 根据这些参考资料,
and many many posts on SO ( so please read to the end before flagging as duplicate ), I should rely on copy elision , whose conditions should be satisfied if I pass by value . 以及关于SO的许多帖子( 因此,在标记为重复项之前,请先阅读全文 ),我应该依靠复制省略 ,如果按值传递,应该满足其条件。 Note that there are two copy elisions required, namely: 请注意,需要两个副本省略号:
constructor call -> constructor local variable -> instance variable
as can be seen when turning copy elision off (compile with g++-4.8 -std=c++11 -fno-elide-constructors
): 如关闭复制删除功能所见(使用g++-4.8 -std=c++11 -fno-elide-constructors
编译):
X constructor
X move
X copy
A constructor
1
So there is one move
step and one copy
step, which should both go away if I turn copy elision on (compile with g++-4.8 -std=c++11 -O3
): 因此,有一个move
步骤和一个copy
步骤,如果我打开复制省略功能(使用g++-4.8 -std=c++11 -O3
编译),那么这两个步骤都应该消失:
X constructor
X copy
A constructor
1
Bummer, the copy
step remained! Bummer, copy
步骤仍然存在!
Can I get any better with any other variation of std::move()
, std::forward
or passing as rvalue-reference
? 使用std::move()
, std::forward
或作为rvalue-reference
传递的任何其他变体,我可以获得更好的结果吗?
struct B {
X x;
B(X x) : x(std::move(x)) {msg("B");}
};
struct C {
X x;
C(X x) : x(std::forward<X>(x)) {msg("C");}
};
struct D {
X x;
D(X&& x) : x(std::move(x)) {msg("D");}
};
int main() {
B b(X(2));
std::cout << b.x.i << "\n\n";
C c(X(3));
std::cout << c.x.i << "\n\n";
D d(X(4));
std::cout << d.x.i << "\n\n";
}
which produces the output: 产生输出:
X constructor
X move
B constructor
2
X constructor
X move
C constructor
3
X constructor
X move
D constructor
4
OK, I turned the copy
into a move
, but this is not satisfactory! OK,我把copy
到一个move
, 但这并不理想!
Next, I tried to make the instance variable x
a reference X&
: 接下来,我尝试使实例变量x
成为引用X&
:
struct E {
X& x;
E(X x) : x(x) {msg("E");}
};
int main() {
E e(X(5));
std::cout << e.x.i << "\n\n";
}
which produces: 产生:
X constructor
E constructor
1690870696
Bad idea! 馊主意! I got rid of the move
but the rvalue
instance that x
was referencing to got destroyed under my seat, so the last line prints garbage instead of 5
. 我摆脱了这一move
但是x
所引用的rvalue
实例在我的座位下被破坏了,所以最后一行显示的是垃圾而不是5
。 Two notes: 两个注意事项:
g++-4.8
didn't warn me of anything, even with -pedantic -Wall -Wextra
g++-4.8
没有警告我任何事情,即使使用-pedantic -Wall -Wextra
5
when compiled with -O0
用-O0
编译时,程序打印5
So this bug may go unnoticed for quite a while! 因此,此错误可能会在相当长的一段时间内被忽略!
So, is this a hopeless case? 那么,这是没有希望的情况吗? Well no: 好吧:
struct F {
X& x;
F(X& x) : x(x) {msg("F");}
};
int main() {
X x(6);
F f(x);
std::cout << f.x.i << "\n";
}
prints: 印刷品:
X constructor
F constructor
6
Really? 真? No fancy new C++11
features, no copy elision at the discretion of the compiler, just plain old FORTRAN66-style pass-by-reference does what I want and probably will perform best? 没有花哨的C++11
新功能,没有由编译器决定的复制省略,只是普通的FORTRAN66样式的按引用传递了我想要的,并且可能会表现最佳?
So here are my questions: 所以这是我的问题:
rvalues
? 有什么方法可以rvalues
适用于rvalues
? Did I miss any features? 我错过任何功能吗? lvalue
-reference version really the best, or are there hidden costs in the X x(6)
step? lvalue
引用版本确实是最好的,还是X x(6)
步骤中存在隐藏成本? x
living on after the construction of f
? 在构造f
之后, x
存在会带来任何不便吗? lvalue
reference to an external instance? 我是否可以使用对外部实例的lvalue
引用来支付数据局部性损失? Without going into too much detail of your question, copy elision is basically used as much as possible. 无需过多讨论您的问题,基本上尽量使用复制省略。 Here's a quick demo: 这是一个快速演示:
#include <iostream>
#include <utility>
struct X
{
int n_;
explicit X(int n) : n_(n) { std::cout << "Construct: " << n_ << "\n"; }
X(X const & rhs) : n_(rhs.n_) { std::cout << "X copy:" << n_ << "\n"; }
X(X && rhs) : n_(rhs.n_) { rhs.n_ = -1; std::cout << "X move:" << n_ << "\n"; }
~X() { std::cout << "Destroy: " << n_ << "\n"; }
};
struct A
{
explicit A(X x) : x_(std::move(x)) {};
X x_;
};
struct B
{
X x;
};
int main()
{
A a(X(12));
B b { X(24) };
}
Construct: 12
X move:12
Destroy: -1
Construct: 24
Destroy: 24
Destroy: 12
The one move in x_(std::move(x))
is not elidable, since it doesn't involve a function return. x_(std::move(x))
的一招是不可消除的,因为它不涉及函数返回。 But that's pretty good anyway. 但这还是很好。 And notice how the aggregate b
is indeed initialized "in-place". 并注意聚合b
确实是“就地”初始化的。
Your example F
shows that you're willing to expose the coupling of X
to its ambient class. 您的示例F
表明,您愿意公开X
与其环境等级的耦合。 In that case, you could make a special constructor for A
that constructs the X
directly: 在这种情况下,您可以为A
创建一个特殊的构造函数,该构造函数直接构造X
:
struct A
{
explicit A(X x) : x_(std::move(x)) {};
X x_;
// Better approach
struct direct_x_t {};
static direct_x_t direct_x;
// In our case, this suffices:
A(direct_x_t, int n) : x_(n) {}
// Generally, a template may be better: (TODO: add SFINAE control)
template <typename ...Args> A(direct_x_t, Args &&... args)
: x_(std::forward<Args>(args)...) {}
};
Usage: 用法:
A a(A::direct_x, 36);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.