5.4 Overloading

Overloading in C++
Section titled “Overloading in C++”Overloading Methods
Section titled “Overloading Methods”In C++, function overloading means defining multiple functions with the same name but with different parameter lists (number, types, or order of parameters). The compiler determines which version to call based on the arguments provided at the call site. Overloading allows functions to perform similar tasks on different types of data, improving readability and reducing the need for different function names. For example, an overloaded print() function could handle both int and string arguments, letting you call print(5) or print("Hello") with the same intuitive function name.
Rules for overloading (within a class):
- Multiple methods in the same class share the same name (
add()in this case) but differ in their parameter list (number, types, or order). - The return type alone cannot distinguish overloaded methods.
- The compiler decides which version to call at compile time.
For example:
#include<iostream>#include<string>using namespace std;
class myMaths {public: virtual int add(int a, int b); virtual float add(float a, float b); virtual string add(string a, string b);};
int myMaths::add(int a, int b) { return (a+b);}
float myMaths::add(float a, float b) { return (a+b);}
string myMaths::add(string a, string b) { return (a+b);}
int main() { myMaths m;
cout << "1 + 2 = " << m.add(1,2) << "\n"; cout << "1.5 + 2.0 = " << m.add(1.5f,2.0f) << "\n"; cout << "one + two = " << m.add(string("one"), string("two")) << "\n"; return 0;}This code gives the output:
1 + 2 = 3 1.5 + 2.0 = 3.5 one + two = onetwoNote: Methods cannot be distinguished by return type alone; the parameter list must differ (in type or number of arguments).
It is also important to recognise that this example is somewhat misleading. Each add() method in the implementation contains exactly the same statement: return (a + b);
So how does the + operator manage to work with int, float, and even string values? The answer lies in operator overloading—the ability of C++ to provide different behaviours for the same operator depending on the types of its operands.
🧩Knowledge Check
Section titled “🧩Knowledge Check”Which of the following is a strict requirement when overloading functions within the same C++ class?
Operator Overloading
Section titled “Operator Overloading”C++ allows programmers to associate the standard predefined operators (such as +, -, =, ==, etc.) with specific functionality for their own classes. A common example is the std::string class, where the + operator performs concatenation and the = operator performs assignment.
Similarly, we can overload predefined operators to make them meaningful for user-defined classes. For instance, in the Account class we might overload the + operator to allow two Account objects to be summed appropriately. For example in the operatorOverloading.cpp example:
// Operator Overloaded Account Example#include<iostream>#include<string>using namespace std;
class Account{ protected: int accountNumber; float balance; string owner;
public: Account(string owner, float aBalance, int anAccountNumber); Account(float aBalance, int anAccountNumber); Account(int anAccountNumber);
Account operator + (Account); bool operator == (Account);
virtual void display(); virtual void makeLodgement(float); virtual void makeWithdrawal(float);};
Account::Account(string anOwner, float aBalance, int anAccNumber): accountNumber(anAccNumber), balance(aBalance), owner (anOwner) {}
Account::Account(float aBalance, int anAccNumber) : accountNumber(anAccNumber), balance(aBalance), owner ("Not Defined") {}
Account::Account(int anAccNumber): accountNumber(anAccNumber), balance(0.0f), owner ("Not Defined") {}
Account Account::operator + (Account a){ return Account(owner, balance + a.balance, accountNumber);}
bool Account::operator == (Account a) { if ((a.balance == balance) && (a.owner == owner) && (a.accountNumber == accountNumber)) { return true; } else return false;}
void Account::display() { cout << "account number: " << accountNumber << " has balance: " << balance << " Euro" << "\n"; cout << "This account is owned by: " << owner << "\n";}
void Account::makeLodgement(float amount) { balance = balance + amount;}
void Account::makeWithdrawal(float amount) { balance = balance - amount;}
int main() { Account a = Account(10.50, 123456); a.display(); Account b = Account("Derek Molloy", 35.50, 123457); b.display(); Account c = Account("Derek Molloy", 35.50, 123457); if (b == c) { cout << "b and c are equal!" << "\n"; }
Account d = b + a; d.display(); return 0;}The full source code for this example is in OperatorOverloadAccount.cpp. In this case, the + operator has been overloaded to add the balance of the object on the right-hand side (RHS) of the operator to the balance of the object on the left-hand side (LHS). Other attributes, such as accountNumber or owner, are not added, as this would not make sense in the context of accounts.
The overloaded method creates a new Account object and returns it. This returned object is then assigned to the variable on the LHS of the assignment when the statement: Account d = b + a; is executed. The output from this code is:
account number: 123456 has balance: 10.5 EuroThis account is owned by: Not Definedaccount number: 123457 has balance: 35.5 EuroThis account is owned by: Derek Molloyb and c are equal!account number: 123457 has balance: 46 EuroThis account is owned by: Derek MolloyIn the line of code: Account d = b + a; we can think of this as happening in two stages:
Stage 1 – The operator call
The expression b + a invokes the overloaded + operator. Conceptually, you can imagine this as if the operator were replaced with an add() method, i.e., b.add(a). In fact, you could even think of it as b.+(a). In this example, rather than directly modifying b, the operator creates and returns a new Account object.
- The first element (
b) is the object the operator is called on. - The second element (
a) is passed as the parameter.
Stage 2 – The assignment
The result of b + a (a new Account object) is then assigned to d by the statement:
d = (b + a);Because the operator implementation is written inside the Account class, it has full access to the protected members of both objects, allowing it to read their balances (and other states if required).
In this example, the == operator is also overloaded, enabling comparison between two Account objects. The specific states compared are up to the programmer’s design. For instance, two accounts might be considered equal if they have the same owner and balance, or the comparison might also include the accountNumber. The == operator can therefore have a class-specific definition of equality.
Overloading the Assignment Operator (Advanced)
The assignment operator = (aka the copy assignment operator) is slightly more complicated, because you need to be able to chain operations (e.g., a = b = c), which means that the operator must return a reference to the value assigned. The code is in the file operatorOverloadingAssignment.cpp.
#include <iostream>using namespace std;
class Number{private: int a;public: Number(int a); Number & operator = (const Number & source); void display();};
Number::Number(int a): a(a) {}
void Number::display(){ cout << "The value of the object at address " << this << " is " << a << "\n";}
Number & Number::operator = (const Number & source){ if (this!= &source){ // make sure we are not copying from itself a = source.a; } return *this; // return the reference (value of pointer this)}
int main(){ Number x(5), y(4), z(3); x.display(); y.display(); z.display(); cout << "Performing the assignment operator now! " << "\n"; z = y = x; x.display(); y.display(); z.display(); return 0;}Results in the following output:
The value of the object 0x62ff0c is 5The value of the object 0x62ff08 is 4The value of the object 0x62ff04 is 3Performing the assignment operator now!The value of the object 0x62ff0c is 5The value of the object 0x62ff08 is 5The value of the object 0x62ff04 is 5Non-Member Operators (Advanced)
Operator overloading is powerful and flexible, but it can also introduce complexity. For example, using the Number class introduced earlier, you can overload the + operator to support the following expression (where n is an object of type Number):
n = n + 5;This is straightforward: you would write an overloaded + operator that accepts an int and adds it to the value stored in the object n. In this case, the integer 5 is passed as an argument to the operator.
However, the reverse expression introduces a complication:
n = 5 + n;Here, the compiler first attempts to call the + operator on the integer type (int), expecting it to accept a Number object as the right-hand side. Since such an operator does not exist for int, this expression fails to compile.
The solution is to define a non-member operator function, which allows the addition to work regardless of the order of operands (operatorOverloadNonMember.cpp):
#include <iostream>using namespace std;
class Number {private: int a;public: Number(int a); void display(); int getValue() const { return a; } // getter does not modify object};
Number::Number(int a) : a(a) {}
void Number::display() { cout << "The value of the object at address " << this << " is " << a << "\n";}
// Non-member operator overload to support both x + y and y + x.// Note: this works because we provided a getter method.// (Friend functions could also be used for direct access.)Number operator+(const Number& left, const Number& right) { return Number(left.getValue() + right.getValue());}
int main() { Number x(5); x = 3 + x; x = x + 2; x.display(); return 0;}This gives the output:
The value of the object at address 0x62ff04 is 10Overloading the Pre- and Post-Increment Operators (Advanced)
An unusual case of operator overloading worth discussing is the pre-increment (++x) and post-increment (x++) operators. Although they use the same symbol (++), their behaviour differs depending on whether the operator appears before or after the variable. The same principles apply to the pre-decrement (--x) and post-decrement (x--) operators.
The following example shows how these operators can be implemented in C++ for a simple Number class. While the class itself is minimal, the technique can be applied to more complex classes (operatorOverloadIncrement.cpp).
#include <iostream>using namespace std;
class Number {private: int a;public: Number(int a); Number& operator++(); // pre-increment (++x) Number operator++(int); // post-increment (x++), 'int' is a dummy parameter void display();};
Number::Number(int a) : a(a) {}
// Pre-increment: increments before returning the objectNumber& Number::operator++() { a++; return *this;}
// Post-increment: increments after returning the original valueNumber Number::operator++(int) { Number temp = *this; // make a copy of the current object a++; return temp;}
void Number::display() { cout << "The value of the object at address " << this << " is " << a << "\n";}
int main() { Number x(5), y(0), z(0);
y = x++; // post-increment cout << "Displaying y = x++ :" << "\n"; y.display();
z = ++x; // pre-increment cout << "Displaying z = ++x :" << "\n"; z.display();
cout << "Displaying x after both operations :" << "\n"; x.display();
return 0;}Important notes:
- The dummy
intparameter in the post-increment operator is a convention used to distinguish it from the pre-increment version. It is always of typeint, regardless of the class. - The post-increment operator is more computationally expensive than pre-increment, as it must create and return a copy of the object.
Example Output:
Displaying y = x++ :The value of the object at address 0x62ff08 is 5Displaying z = ++x :The value of the object at address 0x62ff04 is 7Displaying x after both operations :The value of the object at address 0x62ff0c is 7🧩Knowledge Check
Section titled “🧩Knowledge Check”Overloading the Addition Operator
Why does the overloaded copy assignment operator (operator=) typically return a reference to the current object (*this)?
Why is an overloaded post-increment operator (x++) generally more computationally expensive than pre-increment (++x)?
Overloaded Constructors
Section titled “Overloaded Constructors”A constructor can take parameters to initialise the state of an object. However, there may be different ways to determine the initial state, depending on the information available.
For example, when creating an account, you might want to:
- Specify the account number, owner’s name, and balance.
- Specify the account number and owner’s name, with the balance defaulting to
0.0. - Specify the account number, owner’s name, initial balance, and a referee.
- Or other combinations, depending on the application.
To support these variations, constructors in C++ can be overloaded.
Example: Basic Bank Account with Multiple Constructors (AccountMultipleConstructors.cpp)
#include <iostream>#include <string>using namespace std;
class Account {protected: int accountNumber; float balance; string owner;
public: Account(string owner, float aBalance, int anAccountNumber); Account(float aBalance, int anAccountNumber); Account(int anAccountNumber);
virtual void display(); virtual void makeLodgement(float); virtual void makeWithdrawal(float);};
// Constructor with all parametersAccount::Account(string anOwner, float aBalance, int anAccNumber) : accountNumber(anAccNumber), balance(aBalance), owner(anOwner) {}
// Constructor with account number and balance onlyAccount::Account(float aBalance, int anAccNumber) : accountNumber(anAccNumber), balance(aBalance), owner("Not Defined") {}
// Constructor with account number onlyAccount::Account(int anAccNumber) : accountNumber(anAccNumber), balance(0.0f), owner("Not Defined") {}
void Account::display() { cout << "Account number: " << accountNumber << " has balance: " << balance << " Euro" << "\n"; cout << "This account is owned by: " << owner << "\n";}
void Account::makeLodgement(float amount) { balance += amount;}
void Account::makeWithdrawal(float amount) { balance -= amount;}
int main() { Account a = Account(10.50, 123456); a.display();
Account b = Account("Derek Molloy", 35.50, 123457); b.display();}Example Output:
Account number: 123456 has balance: 10.5 EuroThis account is owned by: Not DefinedAccount number: 123457 has balance: 35.5 EuroThis account is owned by: Derek MolloyAs shown above, the Account object a is created using the constructor that does not specify an owner. The display() method then outputs "Not Defined" as the owner.
Notes on Constructors and Destructors
- A destructor cannot be overloaded: there can only be one destructor, since destructors do not take any parameters. \
- In C++, constructors and destructors are not inherited by derived classes. For example, if a base class defines a constructor with three parameters, the derived class will not automatically have this constructor unless it is explicitly defined.
Reusing Initialisation Logic
A common pattern in C++ is to use a private helper function to avoid repeating initialisation code.
class A { int x; void init(int a) { x = a; /* other setup */ }public: A(int x) { init(x); } A() { init(402); }};In C++11 and later, this can be simplified using constructor delegation:
class A { int x;public: A(int x) : x(x) { /* other setup */ } A() : A(402) {} // delegate to the other constructor};Implicit and Explicit Constructors (Advanced)
It is possible to make a constructor private or to mark it as explicit to control how it is used. For example, making the default constructor private prevents it from being called by the programmer directly, while explicit prevents unintended implicit conversions.
#include <iostream>using namespace std;
class X{private: int a; X(); // made private to prevent default constructor being calledpublic: X(int a); void display();};
X::X(int a): a(a) {}
void X::display(){ cout << "The value of a is " << a << "\n";}
int main(){ X x(5); // cannot use X x; x.display();
// Since there is no default constructor and therefore only // one constructor C++ permits implicit conversion e.g., X y = 'A'; y.display(); return 0;}This might allow you to require the programmer to use one particular constructor, but it also allows implicit conversions to be performed on construction. For example the line to create the object y automatically converts the character 'A' to the number 65 (its ASCII equivalent) giving the output below:
The value of a is 5The value of a is 65If you want to prevent such implicit conversions from being performed, you can use the keyword explicit that then imposes that a variable of the correct type is required. For example, if you change the constructor declaration above to the following:
class X{private: ...public: explicit X(int a); ...};Then an error will arise, preventing the implicit conversion from being compiled:
error: conversion from 'char' to non-scalar type 'X' requested🧩Knowledge Check
Section titled “🧩Knowledge Check”What is the primary purpose of marking a single-parameter constructor with the 'explicit' keyword?
The Copy Constructor
Section titled “The Copy Constructor”The copy constructor is a special constructor that exists implicitly in every C++ class. Its purpose is to create a new object as a copy of an existing object. By default, the compiler provides a copy constructor that performs a member-wise (shallow) copy: all data members of the source object are duplicated into the new object.
:::note This is considered to be a shallow copy because a pointer to a string (or any other equivalent) is copied from the source to the destination, so both objects end up with string pointers that point at the same string in memory. This can be challenging for memory management. If available, a deep copy would create a completely independent copy of the original object that would copy the actual strings to new memory locations. A deep copy is more costly in computational cost and memory, but it is much easier to manage as the destruction of one object has no effect on the other.** :::
The default copy constructor can be invoked automatically when an object is initialised from another object of the same type. For example:
int main() { Account a = Account("Derek Molloy", 35.00, 34234324); Account b(a); // calls the default copy constructor
a.display(); b.display(); // same output: both accounts show balance = 35.00 Euro
a.makeLodgement(100.0);
a.display(); // a now has a balance of 135.00 Euro b.display(); // b still has balance of 35.00 Euro}The full source code is provided in defaultCopyConstructor.cpp. In this example, object b is created as a separate object in memory with identical state values to a. Changes to a do not affect b, and vice versa, since they are distinct objects.
Why Modify the Copy Constructor? While the default copy constructor works in many cases, sometimes it provides too little functionality. For example, suppose we want to create a new account object for the same owner, but with a fresh account number and a zero balance. In such cases, we can define our own copy constructor to specify custom copy behaviour.
class Account {protected: int accountNumber; float balance; string owner;
public: Account(string owner, float aBalance, int anAccountNumber); Account(float aBalance, int anAccountNumber); Account(int anAccountNumber); Account(const Account &sourceAccount); // custom copy constructor
...};
Account::Account(const Account &sourceAccount) : accountNumber(sourceAccount.accountNumber + 1), // new account number balance(0.0f), // zero balance owner(sourceAccount.owner) {} // copy owner nameThe full source code is provided in modifiedCopyConstructor.cpp. Using the same main() function as before, the output is:
account number: 34234324 has balance: 35 EuroThis account is owned by: Derek Molloyaccount number: 34234325 has balance: 0 EuroThis account is owned by: Derek Molloyaccount number: 34234324 has balance: 135 EuroThis account is owned by: Derek Molloyaccount number: 34234325 has balance: 0 EuroThis account is owned by: Derek MolloyHere, the copy constructor copies the owner name but resets the balance and generates a new account number.
Key Points about Copy Constructors \
- The default copy constructor performs a shallow, member-wise copy.
- A user-defined copy constructor allows you to specify custom copy semantics.
- Copy constructors are called when:
- An object is initialised directly from another object (e.g.
Account b(a);). - An object is passed by value to a function.
- An object is returned by value from a function.
- An object is initialised directly from another object (e.g.
- If your class manages resources such as dynamic memory or file handles, you should almost always implement a custom copy constructor (and a matching assignment operator and destructor) to ensure deep copies and avoid resource issues (this is known as the Rule of Three).
🧩Knowledge Check
Section titled “🧩Knowledge Check”Match Copy and Resource Concepts
Move Semantics and the Rule of Five (C++11)
Section titled “Move Semantics and the Rule of Five (C++11)”C++11 introduced move semantics — the ability to transfer ownership of resources from one object to another rather than copying them. This matters most for objects that manage heap-allocated memory, file handles, or network sockets, where copying is expensive and the source object is about to be discarded anyway.
Consider a class that owns a dynamically allocated buffer. Copying it requires allocating new memory and copying every element. Moving it simply transfers the pointer — the source is left empty.
Move Constructor
A move constructor takes an rvalue reference (T&&) and steals the source object’s resources:
class Buffer { int* data; size_t size;public: Buffer(size_t n) : size(n), data(new int[n]) {}
// Move constructor — steal the data, leave source safe to destruct Buffer(Buffer&& source) noexcept : data(source.data), size(source.size) { source.data = nullptr; source.size = 0; }
~Buffer() { delete[] data; }};After the move, source.data is nullptr. The source is in a valid but empty state, and its destructor will safely do nothing.
Move Assignment Operator
Buffer& operator=(Buffer&& source) noexcept { if (this != &source) { delete[] data; // release the current resource first data = source.data; size = source.size; source.data = nullptr; source.size = 0; } return *this;}The Rule of Five
The Rule of Three extends to the Rule of Five in modern C++: if a class defines any one of these five special member functions, it should explicitly define or delete all five.
| Special function | Typical trigger |
|---|---|
| Destructor | Object goes out of scope or delete is called |
| Copy constructor | Object b(a); or pass/return by value |
| Copy assignment | b = a; |
| Move constructor | Object b(std::move(a)); or return from a function |
| Move assignment | b = std::move(a); |
std::move(a) is a cast that produces an rvalue reference, signalling that a’s resources may be transferred rather than copied. After a move, a is in a valid but unspecified state and should not be used unless it is reassigned.
🧩Knowledge Check
Section titled “🧩Knowledge Check”Defining a Move Constructor
What is the actual technical function of std::move when applied to an object in modern C++?
According to the Rule of Five introduced in C++11, which special member functions should typically be managed together?
Scope Resolution Operator (::)
Section titled “Scope Resolution Operator (::)”The scope resolution operator in C++ (::) is used to explicitly specify the scope (namespace or class) of a variable or method.
Example: Accessing Global vs Local Variables
int x; // global variableclass A { int x; // member variable virtual void someMethod() { x++; // increments A::x ::x++; // increments the global x }};Here, x++ refers to the class member A::x, while ::x++ refers to the global variable x.
Example: Overloading and Hiding \n The scope resolution operator is also useful when a derived class hides methods from its base class (rather than overriding them).
class A {public: virtual int f(int);};
class B : public A {public: virtual int f(char*); // hides A::f(int)};
int main() { B b; b.f(2); // Error: B::f(char*) hides A::f(int) b.A::f(2); // OK: explicitly call A::f(int) b.f("test"); // OK: calls B::f(char*)}In this example:
B::f(char*)hidesA::f(int)because the signatures differ.- Without scope resolution,
b.f(2)fails sinceB::f(int)does not exist. - Using
b.A::f(2)forces the compiler to call the base class version.
🧩Knowledge Check
Section titled “🧩Knowledge Check”Match Scope and Keyword Concepts
© 2026 Derek Molloy, Dublin City University. All rights reserved.