Skip to content

Module 02: Operator Overloading and Orthodox Canonical Form

Download Official Subject PDF

Key Concepts:

  • Orthodox Canonical Form (OCF)
  • Copy constructor
  • Copy assignment operator
  • Operator overloading
  • Fixed-point numbers

From Module 02 onwards, ALL classes must implement:

class Sample {
public:
Sample(); // 1. Default constructor
Sample(const Sample& other); // 2. Copy constructor
Sample& operator=(const Sample& other); // 3. Copy assignment operator
~Sample(); // 4. Destructor
};

Without proper OCF, classes with dynamic memory will have bugs:

// BAD: No copy constructor or assignment operator
class Bad {
private:
int* _data;
public:
Bad() { _data = new int(42); }
~Bad() { delete _data; }
};
Bad a;
Bad b = a; // Default copy: b._data = a._data (same pointer!)
// When a and b are destroyed: double delete! CRASH!

Creates a NEW object as a copy of an existing object.

class Sample {
private:
int _value;
int* _data;
public:
// Copy constructor
Sample(const Sample& other) : _value(other._value) {
std::cout << "Copy constructor called" << std::endl;
// Deep copy: allocate new memory and copy content
_data = new int(*other._data);
}
};
Sample a;
Sample b(a); // Copy constructor
Sample c = a; // Copy constructor (NOT assignment!)
func(a); // Copy constructor (pass by value)
return a; // Copy constructor (return by value)
// SHALLOW COPY (default, dangerous with pointers)
class Shallow {
int* _ptr;
public:
Shallow(const Shallow& other) {
_ptr = other._ptr; // Both point to same memory!
}
};
// DEEP COPY (correct for pointers)
class Deep {
int* _ptr;
public:
Deep(const Deep& other) {
_ptr = new int(*other._ptr); // New memory, copy value
}
};

Assigns the value of one EXISTING object to another EXISTING object.

class Sample {
public:
Sample& operator=(const Sample& other) {
std::cout << "Copy assignment operator called" << std::endl;
// 1. Check for self-assignment
if (this != &other) {
// 2. Clean up existing resources
delete _data;
// 3. Copy values
_value = other._value;
_data = new int(*other._data);
}
// 4. Return *this for chaining: a = b = c;
return *this;
}
};
Sample a;
Sample b;
b = a; // Assignment operator (b already exists)
Sample a;
a = a; // Self-assignment - would be bug without check!
// Without check:
// 1. delete _data; // Free the memory
// 2. _data = new int(*other._data); // other._data is already deleted!

class Fixed {
private:
int _rawValue;
static const int _fractionalBits = 8;
public:
// Default constructor
Fixed() : _rawValue(0) {
std::cout << "Default constructor called" << std::endl;
}
// Copy constructor
Fixed(const Fixed& other) : _rawValue(other._rawValue) {
std::cout << "Copy constructor called" << std::endl;
}
// Copy assignment operator
Fixed& operator=(const Fixed& other) {
std::cout << "Copy assignment operator called" << std::endl;
if (this != &other)
_rawValue = other._rawValue;
return *this;
}
// Destructor
~Fixed() {
std::cout << "Destructor called" << std::endl;
}
// Getters/Setters
int getRawBits() const {
std::cout << "getRawBits member function called" << std::endl;
return _rawValue;
}
void setRawBits(int const raw) {
_rawValue = raw;
}
};

Defining custom behavior for operators (+, -, *, /, ==, <, <<, etc.) when used with your class.

// As member function
ReturnType operator@(parameters);
// As non-member function (friend or uses public interface)
ReturnType operator@(LeftType, RightType);
class Fixed {
public:
bool operator>(const Fixed& other) const {
return _rawValue > other._rawValue;
}
bool operator<(const Fixed& other) const {
return _rawValue < other._rawValue;
}
bool operator>=(const Fixed& other) const {
return _rawValue >= other._rawValue;
}
bool operator<=(const Fixed& other) const {
return _rawValue <= other._rawValue;
}
bool operator==(const Fixed& other) const {
return _rawValue == other._rawValue;
}
bool operator!=(const Fixed& other) const {
return _rawValue != other._rawValue;
}
};
class Fixed {
public:
Fixed operator+(const Fixed& other) const {
Fixed result;
result._rawValue = _rawValue + other._rawValue;
return result;
}
Fixed operator-(const Fixed& other) const {
Fixed result;
result._rawValue = _rawValue - other._rawValue;
return result;
}
Fixed operator*(const Fixed& other) const {
Fixed result;
// For fixed-point: (a * b) >> fractionalBits
result._rawValue = (_rawValue * other._rawValue) >> _fractionalBits;
return result;
}
Fixed operator/(const Fixed& other) const {
Fixed result;
// For fixed-point: (a << fractionalBits) / b
result._rawValue = (_rawValue << _fractionalBits) / other._rawValue;
return result;
}
};
class Fixed {
public:
// Pre-increment: ++a
Fixed& operator++() {
_rawValue++;
return *this;
}
// Post-increment: a++
Fixed operator++(int) { // int parameter is just a marker
Fixed temp(*this); // Save current value
_rawValue++; // Increment
return temp; // Return old value
}
// Pre-decrement: --a
Fixed& operator--() {
_rawValue--;
return *this;
}
// Post-decrement: a--
Fixed operator--(int) {
Fixed temp(*this);
_rawValue--;
return temp;
}
};
// Must be non-member function (ostream is on the left)
std::ostream& operator<<(std::ostream& os, const Fixed& fixed) {
os << fixed.toFloat();
return os;
}
// Usage
Fixed a(42.42f);
std::cout << a << std::endl; // Prints: 42.4219

A way to represent decimal numbers using integers, with a fixed number of bits for the fractional part.

Integer: 42
Binary: 00101010.00000000
^^^^^^^^ ^^^^^^^^
Integer Fraction
To convert:
- Int to Fixed: int << 8
- Fixed to Int: fixed >> 8
- Float to Fixed: float * 256 (then round)
- Fixed to Float: fixed / 256.0f
class Fixed {
private:
int _rawValue;
static const int _fractionalBits = 8;
public:
// From int
Fixed(const int value) : _rawValue(value << _fractionalBits) {}
// From float
Fixed(const float value)
: _rawValue(roundf(value * (1 << _fractionalBits))) {}
// To int
int toInt() const {
return _rawValue >> _fractionalBits;
}
// To float
float toFloat() const {
return (float)_rawValue / (1 << _fractionalBits);
}
};
  • Faster than floating-point on some hardware
  • Predictable precision
  • No floating-point rounding errors for exact values
  • Used in: graphics, audio, embedded systems

class Fixed {
public:
// Non-const version
static Fixed& min(Fixed& a, Fixed& b) {
return (a < b) ? a : b;
}
// Const version
static const Fixed& min(const Fixed& a, const Fixed& b) {
return (a < b) ? a : b;
}
// Non-const version
static Fixed& max(Fixed& a, Fixed& b) {
return (a > b) ? a : b;
}
// Const version
static const Fixed& max(const Fixed& a, const Fixed& b) {
return (a > b) ? a : b;
}
};
// Usage
Fixed a(2.0f), b(3.0f);
std::cout << Fixed::max(a, b) << std::endl; // 3

class ClassName {
public:
ClassName(); // Default constructor
ClassName(const ClassName& other); // Copy constructor
ClassName& operator=(const ClassName& other); // Assignment operator
~ClassName(); // Destructor
};
OperatorMember?Signature
+, -, *, /YesT operator+(const T&) const
==, !=, <, >Yesbool operator==(const T&) const
++, -- (pre)YesT& operator++()
++, -- (post)YesT operator++(int)
<<Noostream& operator<<(ostream&, const T&)
// 8 fractional bits
int_to_fixed: value << 8
fixed_to_int: value >> 8
float_to_fixed: roundf(value * 256)
fixed_to_float: value / 256.0f