Why are constructed objects, in tuple initialization, copied? - c++

I have a situation which I cannot wrap my head around. I define a non-copy-able struct and want to in-place construct it in a tuple. If I do so, it is copied. I thought the problem may be std::make_tuple, but it isn't.
If instead I construct the object, then move it in the tuple, things work as expected.
Demonstration (I use std::exit to prevent the output of normal destruction).
#include <tuple>
#include <iostream>
#include <cstdlib>
struct MoveMe {
MoveMe(size_t s) : size(s) {}
MoveMe(MoveMe&&) = default;
MoveMe(const MoveMe&) = delete;
MoveMe& operator=(MoveMe&&) = default;
MoveMe& operator=(const MoveMe&) = delete;
~MoveMe() {
std::cout << "Destroyed" << std::endl;
}
size_t size = 0;
};
int main(int, char**) {
std::cout << "Constructed in-place." << std::endl;
auto tuple = std::make_tuple(MoveMe{10});
std::cout << std::endl << "Other tests." << std::endl;
std::tuple<MoveMe> tuple2 = std::tuple<MoveMe>(MoveMe{10});
std::cout << std::endl << "Moved." << std::endl;
MoveMe obj{10};
auto tuple3 = std::make_tuple(std::move(obj));
std::exit(0);
// return 0;
}
Outputs
Constructed in-place.
Destroyed
Other tests.
Destroyed
Moved.
Any ideas why that is? My understanding of rvalues is basic, so my guess is I'm missing something obvious. Thank you.

It isn't copied it is still moved.
In your case, you're creating a temporary (MoveMe{10}) that is then used to move construct the instance of MoveMe in the tuple. After the instance of MoveMe in the tuple is move constructed, the temporary that was moved is destroyed.
You can directly construct the MoveMe object in the tuple by forwarding the arguments
std::cout << "Directly Constructed" << std::endl;
auto tuple = std::tuple<MoveMe>(10);
Which will not result in a temporary being created then destroyed.

Related

Correctly move from a data member of a temporary object

Consider the following C++-Code
#include <iostream>
using namespace std;
struct WrapMe
{
WrapMe() { cout << "WrapMe Default Ctor of: " << this << endl; }
WrapMe(const WrapMe& other) { cout << "WrapMe Copy Ctor of " << this << " from " << &other << endl; }
WrapMe(WrapMe&& other) noexcept { cout << "WrapMe Move Ctor of " << this << " from " << &other << endl; }
~WrapMe() { cout << "Wrap Me Dtor of" << this << endl; }
};
struct Wrapper1
{
WrapMe& data()& { return member; }
WrapMe data()&& { return std::move(member); }
WrapMe member;
};
struct Wrapper2
{
WrapMe& data()& { return member; }
WrapMe&& data()&& { return std::move(member); }
WrapMe member;
};
int main()
{
auto wrapMe1 = Wrapper1().data();
auto wrapMe2 = Wrapper2().data();
}
with the output
WrapMe Default Ctor of: 00000092E7F2F8C4
WrapMe Move Ctor of 00000092E7F2F7C4 from 00000092E7F2F8C4
Wrap Me Dtor of00000092E7F2F8C4
WrapMe Default Ctor of: 00000092E7F2F8E4
WrapMe Move Ctor of 00000092E7F2F7E4 from 00000092E7F2F8E4
Wrap Me Dtor of00000092E7F2F8E4
[...]
Which is the correct way to move from the WrapMe member: Like Wrapper1 (return by value) or like Wrapper2 (return by rvalue-reference) does? Or are both ways equivalent here, as the ouput suggests? If not, why?
WrapMe&& data()&& { return std::move(member); }
This doesn't actually move anything. It just returns a rvalue reference to the member. For example I could do
auto&& wrapMe2 = Wrapper2().data();
and now wrapMe2 will be a dangling reference or
auto w = wrapper2();
auto&& wrapMe2 = std::move(x).data();
Now I have a reference to the member of w without w having changed at all.
Only because the move constructor of WrapMe is called to initialize the wrapMe2 object in the original line, a move operations actually takes place.
The version
WrapMe data()&& { return std::move(member); }
calls the move constructor already to construct the return value. The returned value will never refer to the original object or its member.
The line
auto wrapMe1 = Wrapper1().data();
then calls the move constructor again to initialize wrapMe1 from the return value of .data(). The compiler is however allowed to elide this second move constructor call and instead construct wrapMe1 directly from the expression in the return statement. This is why you see the same result.
With C++17 or later this elision would even be mandatory.
I don't know your use case, so I cannot be sure what the correct approach is, but just guessing on what you want to do:
For consistency between the two overloads, I would use the reference-returning version. Having data provide modifiable access to the member for lvalues, but not for rvalues, would be confusing.
However, you don't really need a member function to do this. Through data the caller has full control over the member anyway, so you could just make the member public from the start and then it could be used in the same way directly.

getting invalid ouput after std::move of an element from std::list

I'm trying to understand about std::move. In my code I'm moving an element from std::list<struct Data> where struct Data internally contains two std::string fields, but I'm not getting expected output. This is my code:
#include <iostream>
#include <string>
#include <list>
struct Data {
std::string topic {};
std::string msg {};
Data(const std::string& topic, const std::string& msg) {
this->topic = topic;
this->msg = msg;
}
};
int main() {
std::list<Data> data_list;
data_list.push_back(Data("A", std::string(1000, 'a')));
data_list.push_back(Data("B", std::string(1000, 'b')));
data_list.push_back(Data("C", std::string(1000, 'c')));
data_list.push_back(Data("D", std::string(1000, 'd')));
data_list.push_back(Data("E", std::string(1000, 'e')));
while (!data_list.empty()) {
std::cout << (void*)&data_list.front() << "\n";
Data&& d1 = std::move(data_list.front());
data_list.pop_front();
std::cout << d1.topic << ", " << d1.msg << "\n";
std::cout << (void*)&d1 << "\n\n";
}
std::cout << std::endl;
}
The issue here is you are not actually moving anything. When you call std::move, nothing is actually moved. What it does do is converts the lvalue that you have into an rvalue, so that it can then be move constructed or move assigned from. That's not what you are doing here though. You use
Data&& d1 = std::move(data_list.front());
which has d1 as an rvalue reference, meaning no move again, it's just a reference to the object in the list. When you pop that element, your reference is now refering to an object that no longer exits, and using it has undefined behavior leading to the output that you are seeing. If you want to move the element, you need
Data d1 = std::move(data_list.front());
and now d1 will use Data's implicit move constructor to move the list element into d1.

vector elements allocated on stack?

#include <iostream>
#include <vector>
using namespace std;
struct A {
int i = 0;
};
void append(vector<A>& v) {
auto a = v.back(); // is a allocated on the stack? Will it be cleaned after append() returns?
++a.i;
v.push_back(a);
}
void run() {
vector<A> v{};
v.push_back(A{}); // is A{} created on the stack? Will it be cleaned after run() returns?
append(v);
for (auto& a : v) {
cout << a.i << endl;
}
}
int main() {
run();
return 0;
}
The code above prints as expected:
0
1
But I have two questions:
is A{} created on the stack? Will it be cleaned after run() returns?
is a allocated on the stack? Will it be cleaned after append() returns?
Update:
#include <iostream>
#include <vector>
using namespace std;
struct A {
int i = 0;
A() { cout << "+++Constructor invoked." << endl; }
A(const A& a) { cout << "Copy constructor invoked." << endl; }
A& operator=(const A& a) {
cout << "Copy assignment operator invoked." << endl;
return *this;
};
A(A&& a) { cout << "Move constructor invoked." << endl; }
A& operator=(A&& a) {
cout << "Move assignment operator invoked." << endl;
return *this;
}
~A() { cout << "---Destructor invoked." << endl; }
};
void append(vector<A>& v) {
cout << "before v.back()" << endl;
auto a = v.back();
++a.i;
cout << "before v.push_back()" << endl;
v.push_back(a);
cout << "after v.push_back()" << endl;
}
void run() {
vector<A> v{};
v.push_back(A{});
cout << "entering append" << endl;
append(v);
cout << "exited append" << endl;
for (auto& a : v) {
cout << a.i << endl;
}
}
int main() {
run();
return 0;
}
Output:
+++Constructor invoked.
Move constructor invoked.
---Destructor invoked.
entering append
before v.back()
Copy constructor invoked.
before v.push_back()
Copy constructor invoked.
Copy constructor invoked.
---Destructor invoked.
after v.push_back()
---Destructor invoked.
exited append
0
0 // I understand why it outputs 0 here. I omitted the actual work in my copy/move constructors overloads.
---Destructor invoked.
---Destructor invoked.
I updated the code in my question, adding the copy/move constructors. I found copy constructor was called 3 times in append. I understand auto a = v.back(); needs a copy, But the two other copies maybe should be avoided?
The C++ specification doesn't actually say.
With v.push_back(A{}) the A{} part creates a temporary object, which is then moved or copied into the vector, and then the temporary object is discarded.
Same with local variables, really, the "stack" is actually never mentioned by the C++ standard, it only tells how life-time should be handled. That a compiler might use a "stack" is an implementation detail.
With that said, most C++ compilers will use the "stack" to store local variables. Like for example the variable a in the append function. As for the temporary object created for v.push_back(A{}) you need to check the generated assembly code.
For the life-times, the life-time of the temporary object A{} ends as soon as the push_back function returns. And the life-time of a in the append function ends when the append function returns.
In this function
void append(vector<A>& v) {
auto a = v.back(); // is a allocated on the stack? Will it be cleaned after append() returns?
++a.i;
v.push_back(a);
}
the variable a has the automatic storage duration and is a local variable of the function. It will not be alive after exiting the function.
In this function
void run() {
vector<A> v{};
v.push_back(A{}); // is A{} created on the stack? Will it be cleaned after run() returns?
append(v);
for (auto& a : v) {
cout << a.i << endl;
}
}
again the variable v has the automatic storage duration and is a local variable of the function. When the function will finish its execution the variable will be destroyed. And all elements of the vector (that are placed in the heap) also will be destroyed due to the destructor of the vector.
Consider the following demonstrative program.
#include <iostream>
#include <vector>
struct A {
int i = 0;
};
int main()
{
std::vector<A> v;
std::cout << "&v = " << &v << "\n\n";
A a;
std::cout << "&a = " << &a << "\n\n";
v.push_back( a );
std::cout << "&v = " << &v << '\n';
std::cout << "&a = " << &a << '\n';
std::cout << "&v[0] = " << &v[0] << "\n\n";
++a.i;
v.push_back( a );
std::cout << "&v = " << &v << '\n';
std::cout << "&a = " << &a << '\n';
std::cout << "&v[0] = " << &v[0] << '\n';
std::cout << "&v[1] = " << &v[1] << "\n\n";
return 0;
}
Its output might look like
&v = 0x7ffc27288dd0
&a = 0x7ffc27288dcc
&v = 0x7ffc27288dd0
&a = 0x7ffc27288dcc
&v[0] = 0x55725232ee80
&v = 0x7ffc27288dd0
&a = 0x7ffc27288dcc
&v[0] = 0x55725232eea0
&v[1] = 0x55725232eea4
As you can see the addresses of the vector v and the object a looks similarly because they are allocated in the same outer block scope of the function and have the automatic storage duration.
&v = 0x7ffc27288dd0
&a = 0x7ffc27288dcc
And they are not changed when new values are pushed on the vector.
However the addresses of the elements of the vector as for example
&v[0] = 0x55725232ee80
&v[0] = 0x55725232eea0
&v[1] = 0x55725232eea4
have a different representation and can be changed when a new elements are added to the vector because the memory for them can be dynamically reallocated.
EDIT: After you updated your question then take into account that when a new element is added to the vector the elements of the vector can be reallocated calling the copy constructor. You can use the method reserve to reserve enough memory to avoid its reallocation and the method emplace_back.
is a allocated on the stack?
There is no such thing as "stack" storage in the language. a has automatic storage.
As far as language implementations are concerned, this typically means that the variable is probably stored in a register, or on stack, or nowhere.
Will it be cleaned after append() returns?
Yes. Automatic variables are destroyed automatically when they go out of scope.
is A{} created on the stack?
A{} is a temporary object. The language is a bit vague about the storage class of temporary objects, but it is clear about the lifetime.
Will it be cleaned after run() returns?
In this case, the temporary object is destroyed at the end of the full expression, which is before run returns.
vector elements allocated on stack?
No. Vector elements are created in dynamic storage.
Update
But the two other copies maybe should be avoided?
If your endgoal is to get a vector with two elements, you can avoid all of the copies like this:
std::vector<A> v(2);

Return value optimization with std::pair

I am currently quite puzzled with C++17`s guaranteed RVO and its implications. I understand that for NRVO to kick in, I need to make sure that
to return one and the same instance of an object through all possible return path of the function and
to initialize an associated object with the function call on the call side
Consider a most simple decorated class Widget and I want to allocate a pair of Widgets without copies and then return it
#include<iostream>
#include<utility>
struct Widget {
Widget() { std::cerr << "Default ctor" << std::endl; }
explicit Widget(int i) { std::cerr << "custom ctor" << std::endl; }
Widget(Widget const &) { std::cerr << "copy ctor" << std::endl; }
Widget(Widget &&) { std::cerr << "move ctor" << std::endl; }
Widget &operator=(Widget const &) { std::cerr << "copy assign" << std::endl; }
Widget &operator=(Widget &&) { std::cerr << "move assign" << std::endl; }
int i_;
};
auto foo(){
std::pair<Widget,Widget> w; // default construction
auto &[w1,w2] = w;
// do something with w1 and w2
return w;
}
int main(){
auto f = foo();
}
No copy is made but now I tried to use make_pair
auto foo(){
auto w = std::make_pair<Widget,Widget>({},{}); // Awkward syntax and move construction
auto &[w1,w2] = w;
// do something with w1 and w2
return w;
}
Is this really the only possible alternative if I want to use make_pair? Any why is there move construction involved as compared to the first function?
I think the premise of your question is wrong.
Is this really the only possible alternative if I want to use make_pair?
Why would you use std::make_pair here?
auto w = std::make_pair<Widget,Widget>({},{}); // Awkward syntax and move construction
This really is awkward syntax. Let me explain why I think so...
From cppreference on std::make_pair (emphasize mine):
Creates a std::pair object, deducing the target type from the types of arguments.
The only purpose of std::make_pair is to deduce the types of its arguments, as for example in
std::pair<int,int> x;
x = std::pair<int,int>(3,3); // not nice
x = std::make_pair(3,3); // ok
When default constructing a pair and you know its type, you never had to repeat the template parameters
std::pair<int,int> a = std::pair<int,int>(); // awkward
Neither when you want non-default construction (just dont use auto when its only effect is that you have to spell the type elsewhere in the same line of code)
std::pair<int,int> a{5,4}; // no move/copy
Bottomline:
The alternative is to simply not use std::make_pair when you don't need it.

Member function reference qualified return type

Reading Meyers new book I found something very similar to this:
// compile with
// g++-4.8 --std=c++11 -Wall main3.cc && ./a.out
#include <iostream>
#include <vector>
class Widget
{
public:
using DType = std::vector<int>;
DType& data() & // lvalue
{
std::cout << "data (lvalue) : " << &data_[0] << std::endl;
return data_;
};
DType data() && // rvalue
{
std::cout << "data (rvalue) : " << &data_[0] << std::endl;
return std::move(data_);
};
// Please Note
// int parameter is here to make the overloading possible
// in a single class
DType&& data(int) &&
{
std::cout << "data (rvalue ref): " << &data_[0] << std::endl;
return std::move(data_);
};
private:
DType data_ { 0 };
};
Widget getWidget() { return Widget(); }
int main(int argc, char *argv[])
{
Widget w1;
std::vector<int> d1 = w1.data();
std::cout << "d1 copied : " << &d1[0] << std::endl;
std::vector<int> d2 = getWidget().data();
std::cout << "d2 moved : " << &d2[0] << std::endl;
std::vector<int> d3 = getWidget().data(0);
std::cout << "d3 moved : " << &d3[0] << std::endl;
return 0;
}
My point is very simple:
On my box as I would expect I have these results
data (lvalue) : 0x8e28008
d1 copied : 0x8e28018
data (rvalue) : 0x8e28028
d2 moved : 0x8e28028
data (rvalue ref): 0x8e28038
d3 moved : 0x8e28038
So first vector was copied while the second and third were moved.
You can have two different signature to achieve move operation:
One returning an rvalue
DType data() && // rvalue
and one returing a rvalue reference
DType&& data() &&
They achieve the same result: are there any differences I cannot see ? What is the "best" one?
You can have two different signature to achieve move operation:
That is wrong.
The first signature, the one returning DType, performs a move into the return value. The second signature, the one returning DType&& simply returns a reference. It doesn't move anything.
The move happens in the other code, specifically the part with std::vector<int> d3 =. Initializing a vector from an xvalue performs a move. That is what does the move, not the function. However, other kinds of operations won't perform a move:
// no move, just binding the member to a reference
std::vector<int>&& d3 = getWidget().data(0);
Using the first function, however, the move always happens:
// move into a temporary, and bind *that* to a reference
std::vector<int>&& d2 = getWidget().data();
The second signature is dangerous. It is easy to accidentally return a reference to a temporary with it. It is easy to write misleading client code where you think something got moved out but didn't. There is one sensible use case for returning rvalue references, and that use case was already taken care of by the standard library in the forms of std::move and std::forward.