I am trying to get an rvalue
instance of this class:
#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:
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,
- http://thbecker.net/articles/rvalue_references/section_01.html
- http://www.slideshare.net/oliora/hot-11-2-new-style-arguments-passing
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. 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
):
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
):
X constructor
X copy
A constructor
1
Bummer, the copy
step remained!
Can I get any better with any other variation of std::move()
, std::forward
or passing as 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!
Next, I tried to make the instance variable x
a reference 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
. Two notes:
g++-4.8
didn't warn me of anything, even with-pedantic -Wall -Wextra
- The program prints
5
when compiled with-O0
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?
So here are my questions:
- Is there any way one can get this to work for
rvalues
? Did I miss any features? - Is the
lvalue
-reference version really the best, or are there hidden costs in theX x(6)
step? - Could there be any inconveniences introduced by
x
living on after the construction off
? - Could I pay a data locality penalty for using the
lvalue
reference to an external instance?