Module 02: Operator Overloading and Orthodox Canonical Form
Key Concepts:
- Orthodox Canonical Form (OCF)
- Copy constructor
- Copy assignment operator
- Operator overloading
- Fixed-point numbers
Why Function Overloading Matters
Section titled “Why Function Overloading Matters”In C, if you wanted functions to work with different types, you had to give them different names:
// C approach - different names for different typesint max_int(int a, int b);double max_double(double a, double b);float max_float(float a, float b);C++ introduced function overloading: functions with the same name but different parameter types. The compiler chooses the right one based on the arguments:
// C++ approach - same name, compiler picks the right oneint max(int a, int b);double max(double a, double b);float max(float a, float b);
max(3, 5); // Calls max(int, int)max(3.14, 2.71); // Calls max(double, double)This is a form of ad-hoc polymorphism: the same function name adapts to different types. Operator overloading extends this idea to operators like +, -, <<, etc.
Understanding Operators in This Module
Section titled “Understanding Operators in This Module”This module teaches you how to define custom behavior for operators. Here’s what the new syntax means:
The = Operator (Assignment)
Section titled “The = Operator (Assignment)”Fixed a;Fixed b;a = b; // Calls the assignment operator=assigns one object’s value to another EXISTING object- You can customize it by defining
operator=in your class - The compiler provides a default, but for classes with pointers you MUST write your own
- Unlike the copy constructor (which creates a NEW object), assignment works on EXISTING objects
The & Operator (Address-of in Self-Check)
Section titled “The & Operator (Address-of in Self-Check)”if (this != &other) { // Check for self-assignment&othergets the memory address of theotherobjectthisis a pointer to the current objectthis != &othercompares two addresses to check if they’re the same object- This prevents bugs when someone writes
a = a;
The * Operator (Dereference for Return)
Section titled “The * Operator (Dereference for Return)”return *this; // Return the current object (not the pointer!)*thisdereferences thethispointer to get the actual object- Assignment operators return
*thisso you can chain assignments:a = b = c; - Without the
*, you’d return a pointer, which wouldn’t allow chaining
Overloading Operators: The Syntax
Section titled “Overloading Operators: The Syntax”// As member function:bool operator==(const Fixed& other) const { return _rawValue == other._rawValue;}
// As non-member function (must be friend or use public interface):std::ostream& operator<<(std::ostream& os, const Fixed& fixed) { os << fixed.toFloat(); return os;}operator@(where@is any operator) defines custom behavior for that operator- Member operators get one implicit parameter:
this(the left operand) - Non-member operators need both operands as explicit parameters
<<and>>are usually non-member because the left side isstd::ostream/std::istream(not your class)
The ++ and -- Operators
Section titled “The ++ and -- Operators”// Pre-increment: ++aFixed& operator++() { _rawValue++; // Increment first return *this; // Return the incremented object}
// Post-increment: a++Fixed operator++(int) { // The (int) is just a marker! Fixed temp(*this); // Save current value _rawValue++; // Increment return temp; // Return the OLD value}++and--can be prefix (++a) or postfix (a++)- The
(int)parameter distinguishes postfix from prefix (it’s not used, just a marker) - Pre-increment returns by reference (faster, returns the object itself)
- Post-increment returns by value (slower, returns a copy of the old value)
The << Operator (Bit Shift - Fixed Point Math)
Section titled “The << Operator (Bit Shift - Fixed Point Math)”_rawValue = (_rawValue << 8); // Shift left by 8 bits_rawValue = (_rawValue >> 8); // Shift right by 8 bits<<and>>also perform bitwise shifts (unrelated to stream operators!)<< 8multiplies by 2^8 (256) - shifts bits to the left>> 8divides by 2^8 (256) - shifts bits to the right- Used in fixed-point arithmetic for fractional calculations
- The same symbols do completely different things depending on context!
Copy Constructor with & (const Reference)
Section titled “Copy Constructor with & (const Reference)”Fixed(const Fixed& other); // Copy constructor&after the type means “reference to”constmeans “don’t modify the original”const Fixed&is a “const reference” - efficient (no copy) and safe (read-only)- The copy constructor takes a const reference to the source object
- Without
&, you’d create infinite recursion (copy constructor calling itself!)
1. Orthodox Canonical Form (OCF)
Section titled “1. Orthodox Canonical Form (OCF)”Design Philosophy: OCF is not just a rule - it’s a design pattern that ensures all your classes have a consistent, predictable interface. When every class follows OCF:
- Other programmers know what to expect from any class
- Copy/assignment behavior is explicit and documented
- Resource management is properly handled
- Your code integrates smoothly with STL containers and algorithms
Think of OCF as establishing a “contract” that every class fulfills.
The Four Required Members
Section titled “The Four Required Members”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};OCF Pattern Diagram
Section titled “OCF Pattern Diagram”┌─────────────────────────────────────────────────────────────────┐│ Orthodox Canonical Form ││ (The Rule of Four) │├─────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────┐ ││ │ Default │───→ Creates object with default values ││ │ Constructor │ MyClass a; ││ └─────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────┐ ││ │ Copy │───→ Creates NEW object from existing ││ │ Constructor │ MyClass b(a); // or MyClass b = a; ││ └─────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────┐ ││ │ Copy Assignment │───→ Assigns to EXISTING object ││ │ Operator │ a = b; // both already exist ││ └─────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────┐ ││ │ Destructor │───→ Cleans up when object is destroyed ││ │ │ (automatically called) ││ └─────────────────┘ ││ │└─────────────────────────────────────────────────────────────────┘Why all four matter: If your class manages dynamic memory (pointers), the compiler-generated defaults will cause double-delete bugs. You must implement all four to properly manage resource lifecycle.
Why OCF Matters
Section titled “Why OCF Matters”Without proper OCF, classes with dynamic memory will have bugs:
// BAD: No copy constructor or assignment operatorclass 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!2. Copy Constructor
Section titled “2. Copy Constructor”Purpose
Section titled “Purpose”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); }};When It’s Called
Section titled “When It’s Called”Sample a;Sample b(a); // Copy constructorSample c = a; // Copy constructor (NOT assignment!)func(a); // Copy constructor (pass by value)return a; // Copy constructor (return by value)Shallow vs Deep Copy
Section titled “Shallow vs Deep Copy”// 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 }};3. Copy Assignment Operator
Section titled “3. Copy Assignment Operator”Purpose
Section titled “Purpose”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; }};When It’s Called
Section titled “When It’s Called”Sample a;Sample b;b = a; // Assignment operator (b already exists)The Self-Assignment Check
Section titled “The Self-Assignment Check”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!4. Complete OCF Example
Section titled “4. Complete OCF Example”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; }};5. Operator Overloading
Section titled “5. Operator Overloading”What is Operator Overloading?
Section titled “What is Operator Overloading?”Defining custom behavior for operators (+, -, *, /, ==, <, <<, etc.) when used with your class.
Syntax
Section titled “Syntax”// As member functionReturnType operator@(parameters);
// As non-member function (friend or uses public interface)ReturnType operator@(LeftType, RightType);Comparison Operators
Section titled “Comparison Operators”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; }};Arithmetic Operators
Section titled “Arithmetic Operators”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; }};Increment/Decrement Operators
Section titled “Increment/Decrement Operators”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; }};Pre vs Post Increment Return Types
Section titled “Pre vs Post Increment Return Types”Understanding why pre-increment returns by reference and post-increment returns by value:
┌─────────────────────────────────────────────────────────────────┐│ Pre-Increment (++a) vs Post-Increment (a++) │├─────────────────────────────────────────────────────────────────┤│ ││ PRE-INCREMENT (++a): ││ ┌─────────────────────────────────────────────────────┐ ││ │ 1. Increment the value │ ││ │ 2. Return *this (the object itself) │ ││ │ │ ││ │ Fixed& operator++() { ┌─────┐ │ ││ │ _rawValue++; ────→ │ 6 │ a (after) │ ││ │ return *this; └─────┘ │ ││ │ } ↑ │ ││ │ Return by reference │ ││ │ (returns the object) │ ││ └─────────────────────────────────────────────────────┘ ││ ││ POST-INCRECREMENT (a++): ││ ┌─────────────────────────────────────────────────────┐ ││ │ 1. Save old value in temp │ ││ │ 2. Increment the value │ ││ │ 3. Return temp (the OLD value) │ ││ │ │ ││ │ Fixed operator++(int) { ┌─────┐ │ ││ │ Fixed temp(*this); │ 5 │ temp (old) │ ││ │ _rawValue++; └─────┘ │ ││ │ return temp; ────→ ↑ │ ││ │ } Return by value │ ││ │ (returns a COPY) │ ││ │ ┌─────┐ │ ││ │ │ 6 │ a (after) │ ││ │ └─────┘ │ ││ └─────────────────────────────────────────────────────┘ ││ ││ KEY DIFFERENCES: ││ ┌────────────────┬──────────────────┬──────────────────┐ ││ │ │ Pre (++a) │ Post (a++) │ ││ ├────────────────┼──────────────────┼──────────────────┤ ││ │ Return type │ Fixed& (ref) │ Fixed (value) │ ││ │ Returns │ The object │ A copy │ ││ │ Can chain? │ Yes: ++++a │ No: a++++ │ ││ │ Performance │ Faster (no copy) │ Slower (copy) │ ││ │ Usage │ Preferred │ When old value │ ││ │ │ │ needed │ ││ └────────────────┴──────────────────┴──────────────────┘ ││ │└─────────────────────────────────────────────────────────────────┘Best Practice: Use pre-increment (++a) when you don’t need the old value—it’s more efficient because it avoids creating a temporary copy.
Stream Insertion Operator (<<)
Section titled “Stream Insertion Operator (<<)”// Must be non-member function (ostream is on the left)std::ostream& operator<<(std::ostream& os, const Fixed& fixed) { os << fixed.toFloat(); return os;}
// UsageFixed a(42.42f);std::cout << a << std::endl; // Prints: 42.42196. Fixed-Point Numbers
Section titled “6. Fixed-Point Numbers”What are Fixed-Point Numbers?
Section titled “What are Fixed-Point Numbers?”A way to represent decimal numbers using integers, with a fixed number of bits for the fractional part.
Example: 8 Fractional Bits
Section titled “Example: 8 Fractional Bits”Integer: 42Binary: 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.0fImplementation
Section titled “Implementation”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); }};Why Fixed-Point?
Section titled “Why Fixed-Point?”- Faster than floating-point on some hardware
- Predictable precision
- No floating-point rounding errors for exact values
- Used in: graphics, audio, embedded systems
7. Static Member Functions
Section titled “7. Static Member Functions”min and max Functions
Section titled “min and max Functions”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; }};
// UsageFixed a(2.0f), b(3.0f);std::cout << Fixed::max(a, b) << std::endl; // 3Quick Reference
Section titled “Quick Reference”OCF Template
Section titled “OCF Template”class ClassName {public: ClassName(); // Default constructor ClassName(const ClassName& other); // Copy constructor ClassName& operator=(const ClassName& other); // Assignment operator ~ClassName(); // Destructor};Operator Overloading
Section titled “Operator Overloading”| Operator | Member? | Signature |
|---|---|---|
+, -, *, / | Yes | T operator+(const T&) const |
==, !=, <, > | Yes | bool operator==(const T&) const |
++, -- (pre) | Yes | T& operator++() |
++, -- (post) | Yes | T operator++(int) |
<< | No | ostream& operator<<(ostream&, const T&) |
Fixed-Point Conversions
Section titled “Fixed-Point Conversions”// 8 fractional bitsint_to_fixed: value << 8fixed_to_int: value >> 8float_to_fixed: roundf(value * 256)fixed_to_float: value / 256.0f