I am new to programming. sorry for my bad english.
I have tried to use rvalue as initialiser to initial objects.
So, according to the code, it would print out what are the used constructor and assignment operator.
But turned out object "what2" and "what3", those don't print out anything.
here is the code:
#include <iostream>
using namespace std;
class X{
public:
int x;
X()= default;
X(int num):x{num}{}
X(const X& y){
x = y.x;
std::cout << "copy constructor" << std::endl;
std::cout << x << std::endl;
}
X& operator=(const X& d){
x = d.x;
std::cout << "copy assignment" << std::endl;
return *this;
}
X(X&& y){
x = y.x;
std::cout << "move constructor" << std::endl;
}
X& operator=(X&& b){
x = b.x;
std::cout << "move assignment" << std::endl;
return *this;
}
};
X operator +(const X& a,const X& b){
X tmp{};
tmp.x = a.x +b.x;
return tmp;
}
int main(int argc, const char * argv[]) {
X a{7} , b{11};
X what1;
cout << "---------------" << endl;
what1 = a+b;
cout << "---------------" << endl;
X what2{a+b};
cout << "---------------" << endl;
X what3 = a+b;
cout << "---------------" << endl;
std::cout << what1.x << std::endl;
std::cout << what2.x << std:: endl;
std::cout <<what3.x << std::endl;
return 0;
}
the output is:
---------------
move assignment
---------------
---------------
---------------
18
18
18
Program ended with exit code: 0
only "what1" uses assignment properly.
so, how can i use rvalue to initial an object? and using operator= to initial an object?
thank you very much.
Your code could result in move operations being used, but your compiler has chosen to elide those moves and allocate the return of operator+ directly at the call site. You can see this happening if you disable copy elision in your compiler (-fno-elide-constructors in GCC or Clang).
Your move constructor and assignment operator will be successfully used in contexts in which copy elision is not permitted, such as this:
X what2 { std::move(what1) }; //can't elide copy, move constructor used
The following code triggers more of your constructors/operators, check it out to see which trigger in what cases
#include <iostream>
using namespace std;
class X{
public:
int x;
X()
{
x = 0;
std::cout << "constructor" << std::endl;
}
X(int num):x{num}
{
std::cout << "list initilizer" << std::endl;
}
X(const X& y){
x = y.x;
std::cout << "copy constructor" << std::endl;
}
X& operator=(const X& d){
x = d.x;
std::cout << "copy assignment" << std::endl;
return *this;
}
X(X&& y){
x = y.x;
std::cout << "move constructor" << std::endl;
}
X& operator=(X&& b){
x = b.x;
std::cout << "move assignment" << std::endl;
return *this;
}
};
X operator +(const X& a,const X& b){
X tmp{};
tmp.x = a.x +b.x;
return tmp;
}
int main(int argc, const char * argv[]) {
X a{7} , b{11};
cout << "---------------" << endl;
X what1;
cout << "---------------" << endl;
what1 = a+b;
cout << "---------------" << endl;
X what2(a+b);
cout << "---------------" << endl;
X what3 = X(a);
cout << "---------------" << endl;
X what4 = X();
cout << "---------------" << endl;
X what5 = std::move(what1);
cout << "---------------" << endl;
what5 = std::move(what1);
cout << "---------------" << endl;
what5 = what1;
cout << "---------------" << endl;
return 0;
}
GCC provides the -fno-elide-constructors option to disable copy-elision. if you want to elide the copy-elision then use flag -fno-elide-constructors. Refer https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization/27916892#27916892 for more detail
I've a situation where I want to call a function with a parameter and return the result into this same argument
foo = f(foo);
In addition, I assume that the parameter x is very large, so I don't want to call its copy constructor, but rather its move constructor. Finally, I don't want to pass the argument by reference because I would like to compose the function f with another function g. Hence, so that things like
foo = g(f(foo));
are possible. Now, with move semantics, this is all mostly possible as demonstrated by the following program
#include <iostream>
struct Foo {
Foo() {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo f(Foo && foo) {
std::cout << "Called f" << std::endl;
return std::move(foo);
}
Foo g(Foo && foo) {
std::cout << "Called g" << std::endl;
return std::move(foo);
}
int main() {
Foo foo;
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
}
The output from this program is:
constructor
Called f
move
move assignment
destructor
Finished with f(foo)
Called f
move
Called g
move
move assignment
destructor
destructor
Finished with g(f(foo))
destructor
which makes sense. Now, what's bothering me is that when we call f the first time, or the composition, the move constructor is followed by the move assignment operator. Ideally, I'd like to use copy elison to prevent any of these constructors from being called, but I'm not sure how. Specifically, the functions f and g call std::move on foo because otherwise the copy, not move, constructor is called. This is specified in the C++ standard under section 12.8.31 and 12.8.32. Specifically,
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the constructor
selected for the copy/move operation and/or the 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, and the destruction of
that object occurs at the later of the times when the two objects
would have been destroyed without the optimization. This elision of
copy/move operations, called copy elision, is permitted in the
following circumstances (which may be combined to eliminate multiple
copies):
— in a return statement in a function with a class return type, when
the expression is the name of a non-volatile automatic object (other
than a function or catch-clause parameter) with the same cvunqualified
type as the function return type, the copy/move operation can be
omitted by constructing the automatic object directly into the
function’s return value
Since we return a function argument, we don't get copy elison. In addition:
When the criteria for elision of a copy operation are met or would be
met save for the fact that the source object is a function parameter,
and the object to be copied is designated by an lvalue, overload
resolution to select the constructor for the copy is first performed
as if the object were designated by an rvalue. If overload resolution
fails, or if the type of the first parameter of the selected
constructor is not an rvalue reference to the object’s type (possibly
cv-qualified), overload resolution is performed again, considering the
object as an lvalue. [ Note: This two-stage overload resolution must
be performed regardless of whether copy elision will occur. It
determines the constructor to be called if elision is not performed,
and the selected constructor must be accessible even if the call is
elided. —end note ]
Since we return a function argument, we return an l-value, so we're forced to use std::move. Now, at the end of the day, I just want the memory moved back into the argument and calling both a move constructor and move assignment operator seems like too much. It feels like there should be a single move or copy elison. Is there a way to accomplish this?
Edit 1
In a longer response to #didierc's answer than a comment would allow, technically, yes, that would work for this situation. At the same time, the greater goal is to allow functions with multiple returns to be composed together in a way where nothing is copied. I can also do this with move semantics, but it requires a trick from C++14 to work. It also exacerbates the issue with lots of moves. However, technically, there's no copies. Specifically:
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
// Now, for our example
struct Foo {
Foo() {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo,Foo> f(Foo && x,Foo && y) {
std::cout << "Called f" << std::endl;
return std::make_tuple <Foo,Foo> (std::move(x),std::move(y));
}
std::tuple <Foo,Foo> g(Foo && x,Foo && y) {
std::cout << "Called g" << std::endl;
return std::make_tuple <Foo,Foo> (std::move(x),std::move(y));
}
int main() {
Foo x,y;
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(foo)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(foo))" << std::endl;
}
This generates
constructor
constructor
Called f
move
move
move assignment
move assignment
destructor
destructor
Finished with f(foo)
Called f
move
move
Called g
move
move
move assignment
move assignment
destructor
destructor
destructor
destructor
Finished with g(f(foo))
destructor
destructor
Basically, the same issue as above occurs: We get move assignments that would be nice if they disappeared.
Edit 2
Per #MooingDuck's suggestion, it's actually possible to return an rref from the functions. Generally, this would be a really bad idea, but since the memory is allocated outside of the function, it becomes a non-issue. Then, the number of moves is dramatically reduced. Unfortunately, if someone tries to assign the result to an rref, this will cause undefined behavior. All of the code and results are below.
For the single argument case:
#include <iostream>
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
data = x.data;
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
data = x.data;
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo && f(Foo && foo) {
std::cout << "Called f: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
Foo && g(Foo && foo) {
std::cout << "Called g: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
int main() {
Foo foo(5);
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
Foo foo2 = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo)) a second time" << std::endl;
std::cout << "foo2.data = " << foo2.data << std::endl;
// Now, break it.
Foo && foo3 = g(f(Foo(4)));
// Notice that the destuctor for Foo(4) occurs before the following line.
// That means that foo3 points at destructed memory.
std::cout << "foo3.data = " << foo3.data << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
Called f: foo.data = 5
move assignment
Finished with f(foo)
Called f: foo.data = 5
Called g: foo.data = 5
move assignment
Finished with g(f(foo))
Called f: foo.data = 5
Called g: foo.data = 5
move
Finished with g(f(foo)) a second time
foo2.data = 5
constructor
Called f: foo.data = 4
Called g: foo.data = 4
destructor
foo3.data = 4. If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
In the multi-argument case
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
// Now, for our example
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo&&,Foo&&> f(Foo && x,Foo && y) {
std::cout << "Called f: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
std::tuple <Foo&&,Foo&&> g(Foo && x,Foo && y) {
std::cout << "Called g: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
int main() {
Foo x(5),y(6);
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(x,y)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y))" << std::endl;
std::tuple <Foo,Foo> x_y = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y)) a second time" << std::endl;
std::cout << "(x.data,y.data) = (" << std::get <0>(x_y).data << ',' <<
std::get <1> (x_y).data << ')' << std::endl;
// Now, break it.
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
// Notice that the destuctors for Foo(7) and Foo(8) occur before the
// following line. That means that x_y2points at destructed memory.
std::cout << "(x2.data,y2.data) = (" << std::get <0>(x_y2).data << ',' <<
std::get <1> (x_y2).data << ')' << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
constructor
Called f: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with f(x,y)
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with g(f(x,y))
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move
move
Finished with g(f(x,y)) a second time
(x.data,y.data) = (5,6)
constructor
constructor
Called f: (x.data,y.data) = (7,8)
Called g: (x.data,y.data) = (7,8)
destructor
destructor
(x2.data,y2.data) = (7,8). If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
destructor
destructor
It seems to me that what you need is not a mechanism to move back and forth a value through a function call, since references do that adequately, but a device to compose functions working that way.
template <void f(Foo &), void g(Foo &)>
void compose2(Foo &v){
f(v);
g(v);
}
Of course, you could make this more generic on the parameter type.
template <typename T, void f(T&), void (...G)(T&)>
void compose(T &v){
f(v);
compose2<T,G...>(v);
}
template <typename T>
void compose(Foo &){
}
Example:
#include <iostream>
//... above template definitions for compose elided
struct Foo {
int x;
};
void f(Foo &v){
v.x++;
}
void g(Foo &v){
v.x *= 2;
}
int main(){
Foo v = { 9 };
compose<Foo, f, g, f, g>(v);
std::cout << v.x << "\n"; // output "42"
}
Note that you could even parameterize the template on the procedure prototype, but at this time on my machine, only clang++ (v3.5) seems to accept it, g++ (4.9.1) doesn't like it.
You can do it without move if you use a little bit of indirection and compiler optimizations:
void do_f(Foo & foo); // The code that used to in in f
inline Foo f(Foo foo)
{
do_f(foo);
return foo; // This return will be optimized away due to inlining
}
Per #MooingDuck's suggestion, it's actually possible to return an rref from the functions. Generally, this would be a really bad idea, but since the memory is allocated outside of the function, it becomes a non-issue. Then, the number of moves is dramatically reduced. Unfortunately, if someone tries to assign the result to an rref, this will cause undefined behavior. All of the code and results are below.
For the single argument case:
#include <iostream>
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
data = x.data;
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
data = x.data;
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo && f(Foo && foo) {
std::cout << "Called f: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
Foo && g(Foo && foo) {
std::cout << "Called g: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
int main() {
Foo foo(5);
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
Foo foo2 = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo)) a second time" << std::endl;
std::cout << "foo2.data = " << foo2.data << std::endl;
// Now, break it.
Foo && foo3 = g(f(Foo(4)));
// Notice that the destuctor for Foo(4) occurs before the following line.
// That means that foo3 points at destructed memory.
std::cout << "foo3.data = " << foo3.data << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
Called f: foo.data = 5
move assignment
Finished with f(foo)
Called f: foo.data = 5
Called g: foo.data = 5
move assignment
Finished with g(f(foo))
Called f: foo.data = 5
Called g: foo.data = 5
move
Finished with g(f(foo)) a second time
foo2.data = 5
constructor
Called f: foo.data = 4
Called g: foo.data = 4
destructor
foo3.data = 4. If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
In the multi-argument case
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
// Now, for our example
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo&&,Foo&&> f(Foo && x,Foo && y) {
std::cout << "Called f: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
std::tuple <Foo&&,Foo&&> g(Foo && x,Foo && y) {
std::cout << "Called g: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
int main() {
Foo x(5),y(6);
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(x,y)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y))" << std::endl;
std::tuple <Foo,Foo> x_y = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y)) a second time" << std::endl;
std::cout << "(x.data,y.data) = (" << std::get <0>(x_y).data << ',' <<
std::get <1> (x_y).data << ')' << std::endl;
// Now, break it.
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
// Notice that the destuctors for Foo(7) and Foo(8) occur before the
// following line. That means that x_y2points at destructed memory.
std::cout << "(x2.data,y2.data) = (" << std::get <0>(x_y2).data << ',' <<
std::get <1> (x_y2).data << ')' << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
constructor
Called f: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with f(x,y)
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with g(f(x,y))
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move
move
Finished with g(f(x,y)) a second time
(x.data,y.data) = (5,6)
constructor
constructor
Called f: (x.data,y.data) = (7,8)
Called g: (x.data,y.data) = (7,8)
destructor
destructor
(x2.data,y2.data) = (7,8). If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
destructor
destructor