What does the copy constructor and move constructor look like behind the scenes, in terms of memory usage?
If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.
I really do not understand what "stealing resources" is either with the move constructor.
Imagine an object containing a member pointer to some data that is elsewhere in memory. For example, a std::string
pointing at dynamically allocated character data. Or a std::vector
pointing at a dynamically allocated array. Or a std::unique_ptr
pointing at another object.
A copy constructor must leave the source object intact, so it must allocate its own copy of the object's data for itself. Both objects now refer to different copies of the same data in different areas of memory (for purposes of this beginning discussion, lets not think about reference-counted data, like with std::shared_ptr
).
A move constructor, on the other hand, can simply "move" the data by taking ownership of the pointer that refers to the data, leaving the data itself where it resides. The new object now points at the original data, and the source object is modified to no longer point at the data. The data itself is left untouched.
That is what makes move semantics more efficient than copy/value sematics.
Here is an example to demonstrate this:
class MyIntArray
{
private:
int *arr = nullptr;
int size = 0;
public:
MyIntArray() = default;
MyIntArray(int size) {
arr = new int[size];
this->size = size;
for(int i = 0; i < size; ++i) {
arr[i] = i;
}
}
// copy constructor
MyIntArray(const MyIntArray &src) {
// allocate a new copy of the array...
arr = new int[src.size];
size = src.size;
for(int i = 0; i < src.size; ++i) {
arr[i] = src.arr[i];
}
}
// move constructor
MyIntArray(MyIntArray &&src) {
// just swap the array pointers...
src.swap(*this);
}
~MyIntArray() {
delete[] arr;
}
// copy assignment operator
MyIntArray& operator=(const MyIntArray &rhs) {
if (&rhs != this) {
MyIntArray temp(rhs); // copies the array
temp.swap(*this);
}
return *this;
}
// move assignment operator
MyIntArray& operator=(MyIntArray &&rhs) {
MyIntArray temp(std::move(rhs)); // moves the array
temp.swap(*this);
return *this;
}
/*
or, the above 2 operators can be implemented as 1 operator, like below.
This allows the caller to decide whether to construct the rhs parameter
using its copy constructor or move constructor...
MyIntArray& operator=(MyIntArray rhs) {
rhs.swap(*this);
return *this;
}
*/
void swap(MyIntArray &other) {
// swap the array pointers...
std::swap(arr, other.arr);
std::swap(size, other.size);
}
};
void copyArray(const MyIntArray &src)
{
MyIntArray arr(src); // copies the array
// use arr as needed...
}
void moveArray(MyIntArray &&src)
{
MyIntArray arr(std::move(src)); // moved the array
// use arr as needed...
}
MyIntArray arr1(5); // creates a new array
MyIntArray arr2(arr1); // copies the array
MyIntArray arr3(std::move(arr2)); // moves the array
MyIntArray arr4; // default construction
arr4 = arr3; // copies the array
arr4 = std::move(arr3); // moves the array
arr4 = MyIntArray(1); // creates a new array and moves it
copyArray(arr4); // copies the array
moveArray(std::move(arr4)); // moves the array
copyArray(MyIntArray(10)); // creates a new array and copies it
moveArray(MyIntArray(10)); // creates a new array and moves it
Should the move constructor be used for dynamically allocated memory or memory on the stack?
Move semantics are most commonly used with pointers/handles to dynamic resources, yes (but there are other scenarios where move semantics can be useful). Updating pointers to data is faster than making copies of data. Knowing that the source object will not need to refer to its data anymore, there is no need to copy the data and then destroy the original, the original can be "moved" as-is from the source object to the destination object.
Where move semantics doesn't help improve efficiency is when the data being "moved" is POD data (plain old data, ie integers, floating-point decimals, booleans, struct/array aggregates, etc). "Moving" such data is the same as "copying" it. For instance, you can't "move" an int
to another int
, you can only copy its value.
Also I was told that if I have code like this: ... that o
would be copied to obj
.
In the example of someFunction(Obj obj)
, yes, since it takes its obj
parameter by value, thus invoking the copy constructor of Obj
to create the obj
instance from o
.
Not in the example of someFunction(Obj &&obj)
or someFunction(const Obj &obj)
, no, since they take their obj
parameter by reference instead, thus there is no new object created at all. A reference is just an alias to an existing object (behind the scenes, it is implemented as a pointer to the object). Applying the &
address-of operator to a reference will return the address of the object that is being referred to. Which is why you see the same address being printed in main()
and someFunction()
in those examples.
My impression was that copying creates a new item in memory and copies the passed data to that object's memory address.
Essentially, yes. It would be more accurate to say that it copies the values of the passed object's member fields to the corresponding member fields of the new object.
So obj
would create a new space in memory and o
's data would be copied to it.
Only if obj
is a copy of o
, yes.