Dynamic Dispatch

Memory Layout

For this simple class:

class Account {
public:
	Account() : balance(0.0) { }
	Account(double initial) : balance(initial) { }

	void credit (double amt) {
		balance += amt;
	}

	void debit (double amt) {
		balance -= amt;
	}

	double get_balance() const {
		return balance;
	}
	std::string type() const {
		return "Acccount";
	}
private:
	double balance;
}

Stack segment:

Code segment:


The memory layout of a derived class would have all of the above plus more at the end.

Object Slicing

When a compiler lays out a derived object in memory, it puts the data of the base class first.
We can convert from a derived class back to its base class.

Virtual (Dynamic Dispatch)

Use the virtual keyword to indicate a method to be overridden by the derived class.


The memory layout of the Account class with virtual function type() would look like this:

Stack segment:

Code segment:


When you look at the derived class, the pointer would look like:

Override

It's a typical mistake to believe you had override a function when you have not. Often because:


The keyword override helps. When you intend to override a function, add the override modifier:

class Account {
public:
	virtual std::string type() const { return "Account"; }
};

class CheckingAccount : public Account {
public:
	std::string type() const override { return "CheckingAccount"; }
};

This way, if you failed to match the functions, it will throw a warning.

Pass by-value vs by-reference

When you pass by-value, a copy constructor is called to create a copy of the passing object. It takes in the passing object as a reference (object slicing happens in the constructor), but the new created object is using the base class memory layout, which means the virtual function is pointing to Account::type().

So, a function that takes in pass-by values will not be able to take advantage of overriding.