Look at this code
#include <iostream>
#include <vector>
class Entity {
public:
Entity(int x, int y) : x(x), y(y) {std::cout << "DEFAULT\n";}
Entity(const Entity& that) {
std::cout << "COPY\n";
this->x = that.x;
this->y = that.y;
}
Entity(Entity&& that) {
std::cout << "MOVE\n";
this->x = that.x;
this->y = that.y;
}
private:
int x, y;
};
class List {
public:
List(){vec.reserve(25);}
void Add(Entity &&en) {
vec.emplace_back(std::move(en)); //invokes the move constructor
}
void Add(int x, int y) {
vec.emplace_back(x, y);
}
private:
std::vector<Entity> vec;
};
int main() {
List L;
L.Add(Entity(10, 20));
L.Add(100, 200);
return 0;
}
I've defined a vector of type Entity
inside the List
class and provided a function Add()
to access this vector. Using the Add(Entity&&)
overload invokes the move constructor meanwhile using the other overload, Add(int, int)
doesn't trigger anything but the default constructor of Entity
.
I know why Add(int, int)
doesn't invoke copy or move constructors. Because std::vector::emplace_back()
utilizes the placement new operator to create the object in-place.
But I don't understand the behavior of the Add(Entity&&)
function. Why is the object getting moved? Is it because it has been created outside of the stack frame of the calling function? If so and if it's possible, how to avoid moving the object?
If I'm totally wrong, please clarify it for me. Thanks in advance.
CodePudding user response:
With emplace_back
you provide arguments that can be directly passed to the Entity::Entity(int,int)
constructor.
When you already have an object of type Entity
your only option is to pass that object to the constructor. The only 2 constructors that take a parameter of type Entity
are the copy and the move constructor. The move constructor is chosen because you pass an rvalue (via std::move
)
how to avoid moving the object?
If you already have the object then the only 2 options are copy or move. What else could there be? If you have the individual parameters needed to construct the object (like your int
, int
) then you can use emplace_back
like you do in that case.
CodePudding user response:
When you call
L.Add(Entity(10, 20));
the following operations happen:
Entity(10, 20) creates a temporary
List::add is called with temporary
std::move() makes the temporary movable
vector::emplace_back is called with the temporary
vector resizes
vector calls construct_at with the temporary
construct_at calls placement new with the temporary
placement new copies/moves the temporary into place
Now the compiler elides all of that except these three things:
Entity(10, 20) creates a temporary
vector resizes
placement new copies/moves constructs the temporary
Unfortunately the compiler does not elide the copy/move construction in the new
call. Unlike Entity(Entity(10, 20))
, where the copy/move construct is optimized out, the new(addr) Entity(Entity(10, 20))
your code basically comes to does not collapse in the same way.
It would be nice if the compiler would (could?) optimize the extra copy/move construct away but it's not defined for new
to do that.