C++ — Move Constructors

Sean Oughton
4 min readJul 6, 2021

--

Why Move Constructors

A Copy Constructor can be called many times automatically due to the the copy semantics of C++. If the Copy Constructors are doing a Deep Copy, this can create a significant performance bottleneck, due to the amount of data being allocated on the heap/free store.

  • C++ 11 introduced move semantics and the Move Constructor.
  • Move constructors are optional, if you don’t provide it then the copy constructor will be called.
  • Recommended when you have a raw pointer

What does a Move Constructor Do

Move Constructors moves an object rather than copying it. Instead of making a Deep Copy, it moves the resource on the heap/free store.

This differs from member wise copy, because it does not copy the data. Rather, it nulls out the pointer in the source pointer. So you get an object that owns the data on the heap/free store(points to the data on the heap/free store) and the original object with a null pointer.

Syntax

class Move {
private:
int *data; // raw pointer
public:
void set_data_value(int d) { *data = d; }
int get_data_value() { return *data; }
Move(int d); // Constructor
Move(const Move &source); // Copy Constructor
Move(Move &&source); // Move Constructor
~Move(); // Destructor
};
Move::Move(Move &&source)
: data{source.data} {
source.data = nullptr; // Steal the data and then null out the source pointer
}

No Const qualifier is used for the param source, because you need to be able to modify the param source to nullify its pointer.

The example above:

  • Copies a pointer data member — copies source.data to data
  • Nullifies source.data — source.data = nullptr;
  • uses && — the R-value reference operator

Move Constructor deals in R values

Sometimes when C++ code is executed, the compiler creates unnamed temporary values.

Ex:

int total {0};
total = 100 + 200;

In the above example:

  • 100 + 200 is evaluated and 300 is stored in an unnamed temporary value.
  • Then 300 is stored in the variable total.
  • The temp value is discarded.

This same thing happens with objects as well. Because of this, you have to be able to tell whether an expression evaluates to an L Value or an R Value.

R values are what the Move Constructor deals with.

Rule of Thumb for L and R values

  • L Value — When you can refer to an object by name, or you can follow a pointer to get an object, then that object is addressable and it is an L value
  • R values are everything else.

In terms of move semantics, R values are temporary objects that are created by the compiler and objects returned from methods.

R Value references

  • Used in move semantics for perfect forwarding (moving the pointer around)
  • References to temporary objects
  • Used by the Move Constructor and the Move Assignment Operator to efficiently move an object rather than copy it
  • && — the R-value reference operator
  • If you try and assign an L value to an R value reference you get a compiler error
int x{100}
int &l_ref = x; // l-value reference
l_ref = 10; // change x to 10

int &&r_ref = 200; // r-value ref
r_ref = 300; // change r_ref to 300

int &&x_ref = x; // Throws a compiler error
  • The code below throws an error because the compiler cannot bind non-const L-value reference of type int& to an R value type of int
  • The opposite of this is also true, you cannot pass an L value into a function as a param if the function is looking for an R value

Bonus — ‘this’ Pointer

  • this is a reserved keyword in C++.
  • It contains the address of the current object, so it is a pointer to the object.
  • It can only be used in the Class scope.
  • All member access is done via the this pointer.

This can be used to:

  • Access data members and methods
  • Determine if two objects are the same
  • yield the current object — by dereferencing
*this

Ex of using this to resolve ambiguities in param vs member attributes

void Account::set_balance(double bal) {
balance = bal; // this->balance is implied
}
void Account::set_balance(double bal) {
balance = balance; // Abmiguous as to which balance
}
void Account::set_balance(double balance) {
this->balance = balance; // Unambiguous
}

Ex of using this to determine object identity

The code below test if two objects are the same object by comparing this to the the address.

int Account::compare_balance(const Account &other) {
if (this == &other) {
std:cout << "The same objects" << std::endl;
}
}
frank_account.compare_balance(frank_account);

Bonus #2 — Using Const with Classes

You can create const vars which cannot be changed. You can also pass arguments to class members as const, which prevents them from being modified in the function. You can also create const objects.

Creating a const objects

const Player villian {"Villain", 100, 55};

Calling member functions on const objects

const Player villian {"Villain", 100, 55};

void display_player_name(const Player &p) {
cout << p.get_name() << endl;
}
display_player_name(villian);

This will throw an error, because the compiler assumes that the method could change the object. When p.get_name() is called, the this pointer is not expecting a const object, so get name could potentially change the object.

The solution is to tell the compiler that specific methods will not modify the object. This is accomplished by adding the const modifier before the semicolon in the method prototype.

class Player {
private:
...
public:
std::string get_name() const;
}
  • The compiler will allow this method to be called on const objects.
  • The compiler will also throw an error if the method tries to modify any of the object attributes.
  • It is best practice to add the const modifier to any method that does not. modify the object

--

--