Unit 4: Operator Overloading, Type Conversion and Inheritance

CSE202 — Object Oriented Programming 8 min read

I. Object-Oriented Extension and Reuse

Object-oriented programming allows user-defined types to behave like built-in types and enables new classes to reuse or specialize existing classes. In C++, operator overloading defines operations on class objects, type conversion connects different representations, and inheritance establishes relationships between classes.

  • Defining properties:
    • Operator overloading: Assigns class-specific meaning to an existing C++ operator such as +, -, or ==.
    • Type conversion: Transforms a value from a basic type to a class type, or from a class type to a basic type.
    • Inheritance: Creates a derived class from one or more base classes.
    • Polymorphic behavior: Allows a derived class to replace or override inherited behavior.
    • Object relationships: Inheritance represents an “is-a” relationship, while aggregation and composition represent “has-a” relationships.
  • Core conventions:
    • Operators cannot be invented; only existing operators can be overloaded.
    • Operator precedence, associativity, and number of operands cannot be changed.
    • Constructors establish object state; destructors release resources.
    • Access specifiers private, protected, and public control member visibility.

II. Operator Overloading — Class-Specific Operator Behavior

Operator overloading gives existing C++ operators a meaningful interpretation for operands of a user-defined class.

A. Unary operator overloading

Unary operator overloading defines an operator that acts on one operand, such as unary -, ++, --, or !.

  • Member form: A unary member operator takes no explicit argument because the invoking object is the operand.
  • Return value: Returning a new object suits operators such as unary minus; prefix increment commonly returns a reference.
  • Postfix distinction: Postfix ++ or -- uses an unused int parameter to distinguish it from the prefix form.
  • Concrete example:
CPP
class Number {
    int value;
public:
    Number(int v) : value(v) {}

    Number operator-() const {
        return Number(-value);
    }

    Number& operator++() {       // Prefix ++
        ++value;
        return *this;
    }

    Number operator++(int) {     // Postfix ++
        Number old = *this;
        ++value;
        return old;
    }
};
  • Meaning: If n contains 5, then -n produces an object containing -5; ++n increments before returning, whereas n++ returns the previous value.

B. Binary operator overloading

Binary operator overloading defines an operation involving two operands, such as +, -, *, ==, or <.

  • Member form: The left operand is the invoking object and the right operand is the single explicit parameter.
  • Non-member form: Both operands are parameters; this supports symmetric conversions and is often declared friend when private data must be accessed.
  • Result discipline: Arithmetic operators generally return a new object and should not modify their operands.
  • Concrete example:
CPP
class Complex {
    double real, imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    Complex operator+(const Complex& rhs) const {
        return Complex(real + rhs.real, imag + rhs.imag);
    }
};
  • Operation: For a = (2, 3) and b = (4, 1), a + b returns (6, 4).
  • Restrictions: Operators such as ., .*, ::, ?:, and sizeof cannot be overloaded; at least one operand must be a user-defined type.

III. Type Conversion — Connecting Representations

Type conversion allows objects and fundamental values to cross class boundaries through constructors or conversion functions.

A. Basic type to class type conversion

Basic type to class type conversion constructs a class object from a fundamental value such as int, double, or char.

  • Mechanism: A single-argument constructor acts as a converting constructor.
  • Implicit conversion: Without explicit, the compiler may invoke the constructor automatically during initialization or assignment.
  • Controlled conversion: The explicit keyword prevents unintended implicit conversions.
  • Concrete example:
CPP
class Distance {
    double metres;
public:
    explicit Distance(double m) : metres(m) {}
};

Distance d(12.5);              // Direct conversion
Distance d2 = Distance(8.0);   // Explicit construction
  • Interpretation: The basic value 12.5 becomes the metres state of a Distance object.
  • Design rule: Use explicit when a basic value does not unambiguously represent the complete class object.

B. Class type to basic type conversion

Class type to basic type conversion extracts a fundamental representation from an object through a conversion function.

  • Syntax: A conversion function is written as operator type() and has no declared return type.
  • Conditions: It must be a non-static member function and takes no explicit parameters.
  • Const correctness: Marking it const permits conversion of constant objects.
  • Concrete example:
CPP
class Distance {
    double metres;
public:
    Distance(double m) : metres(m) {}

    explicit operator double() const {
        return metres;
    }
};

Distance d(7.5);
double m = static_cast<double>(d);
  • Result: m receives 7.5.
  • Safety: An explicit conversion operator requires a cast and avoids accidental use in unrelated arithmetic or Boolean contexts.

IV. Inheritance Structures — Forms of Class Derivation

A derived class inherits accessible members from a base class and may add data or behavior. These structures describe how classes are connected, independently of their access mode.

A. Derived class and base class

A base class provides existing state or behavior, while a derived class extends or specializes it.

  • Base class: Declares reusable members, such as Vehicle::start().
  • Derived class: Inherits accessible members and adds specialized members, such as Car::openBoot().
  • Declaration:
CPP
class Vehicle {
public:
    void start() {}
};

class Car : public Vehicle {
public:
    void openBoot() {}
};
  • Relationship: A Car is a Vehicle, so a Car object can call both start() and openBoot().
  • Access boundary: Base-class private members exist inside the derived object but cannot be accessed directly by derived-class functions.

B. Simple inheritance

Simple inheritance occurs when one derived class inherits from exactly one base class.

  • Structure: Base -> Derived.
  • Purpose: It adds specialization without combining unrelated class interfaces.
  • Example: SavingsAccount may inherit common account details from Account.
  • Benefit: Shared operations such as deposit() remain in the base class, reducing duplication.

C. Multilevel inheritance

Multilevel inheritance forms a chain in which one derived class becomes the base class of another.

  • Structure: A -> B -> C.
  • Example: LivingThing -> Animal -> Dog.
  • Propagation: Dog receives accessible members originating in both Animal and LivingThing.
  • Construction order: Constructors run from the highest base to the most-derived class: LivingThing, then Animal, then Dog.
  • Risk: Deep inheritance chains can make behavior and member ownership difficult to trace.

D. Multiple inheritance

Multiple inheritance allows one derived class to inherit directly from two or more base classes.

  • Structure: class C : public A, public B.
  • Example: A SmartPrinter may inherit from both Printer and Scanner.
  • Benefit: The derived class combines distinct interfaces or capabilities.
  • Risk: Equal member names in different bases cause ambiguity, and repeated inheritance from a common ancestor creates duplicate base subobjects.

E. Hierarchical inheritance

Hierarchical inheritance creates several derived classes from one common base class.

  • Structure: One base class branches into multiple derived classes.
  • Example: Shape may be the base of Circle, Rectangle, and Triangle.
  • Reuse: Common data or functions, such as colour or draw(), are declared once in Shape.
  • Specialization: Each derived class supplies behavior appropriate to its own geometry.

V. Inheritance Access Modes — Controlling Visibility

The inheritance mode determines how the base class’s public and protected members are treated inside the derived class; base private members remain directly inaccessible.

A. Private inheritance

Private inheritance transforms inherited public and protected members into private members of the derived class.

  • Syntax: class D : private B.
  • Client access: Outside code cannot use the inherited public interface through a D object.
  • Further derivation: Classes derived from D cannot directly access those inherited members.
  • Use: It expresses implementation reuse rather than a public “is-a” relationship.
  • Default: Inheritance is private when a C++ class omits the inheritance specifier.

B. Protected inheritance

Protected inheritance transforms inherited public and protected members into protected members of the derived class.

  • Syntax: class D : protected B.
  • Client access: Outside code cannot directly call inherited members through D.
  • Further derivation: Subclasses of D may access those inherited members.
  • Use: It shares implementation through an inheritance hierarchy without exposing the base interface publicly.

C. Public inheritance

Public inheritance preserves base public members as public and base protected members as protected.

  • Syntax: class D : public B.
  • Substitutability: A D object can normally be used where a B object is expected.
  • Interface preservation: Public base operations remain available to clients of the derived object.
  • Use: It is the standard mode for modeling a genuine “is-a” relationship.
  • Default: Inheritance is public when a C++ struct omits the inheritance specifier.

VI. Inherited Behavior and Object Lifetime — Runtime and Structural Rules

Inheritance affects function selection, initialization, destruction, and member lookup throughout the class hierarchy.

A. Overriding member functions

Overriding occurs when a derived class supplies a function matching a virtual function declared in its base class.

  • Requirements: The function name, parameter types, and const qualification must match; compatible covariant returns are allowed for pointers or references.
  • Dynamic dispatch: Calls through a base pointer or reference invoke the derived implementation when the base function is virtual.
  • Verification: The override specifier makes the compiler reject accidental mismatches.
  • Concrete example:
CPP
class Shape {
public:
    virtual void draw() const {}
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() const override {}
};
  • Distinction: A same-named function with different parameters hides base overloads rather than overriding them.

B. Order of execution of constructors and destructors

Construction proceeds from base subobjects to the derived object, while destruction occurs in the exact reverse order.

  • Construction sequence:
    1. Virtual base constructors execute first.
    2. Direct base constructors execute in declaration order.
    3. Data members execute in their declaration order.
    4. The derived constructor body executes last.
  • Destruction sequence:
    1. The derived destructor body executes first.
    2. Data members are destroyed in reverse declaration order.
    3. Direct and virtual bases are destroyed afterward.
  • Multiple inheritance: Base construction follows the order in the class declaration, not the initializer-list order.
  • Polymorphic deletion: A base class used polymorphically needs a virtual destructor so deleting through a base pointer destroys the complete derived object.

C. Resolving ambiguities in inheritance

Ambiguity occurs when the compiler finds more than one eligible inherited member or more than one path to a base subobject.

  • Scope resolution: Qualify the intended base explicitly, such as obj.Printer::start().
  • Derived wrapper: Define a derived member that deliberately selects or combines base implementations.
  • Using declaration: using Base::function; can expose a chosen overload set.
  • Diamond ambiguity: If B and C both inherit A, then a class inheriting both may contain two A subobjects.
  • Resolution choice: Ordinary qualification resolves member-name ambiguity; virtual inheritance resolves duplicated common-base state.

D. Virtual base class

A virtual base class ensures that the most-derived object contains only one shared subobject of a common base in a diamond hierarchy.

  • Declaration:
CPP
class Person {};
class Student : virtual public Person {};
class Employee : virtual public Person {};
class TeachingAssistant : public Student, public Employee {};
  • Effect: TeachingAssistant contains one Person subobject rather than separate Student::Person and Employee::Person subobjects.
  • Initialization: The most-derived class, TeachingAssistant, is responsible for constructing the virtual base.
  • Trade-off: Virtual inheritance removes duplicated state but introduces more complex object layout and initialization rules.

VII. Whole-Part Relationships — Object Ownership

Aggregation and composition model objects that contain or refer to other objects instead of inheriting their interfaces.

A. Aggregation

Aggregation is a weak “has-a” relationship in which the contained object can exist independently of the owner.

  • Lifetime: Destroying the whole does not necessarily destroy the part.
  • Representation: It is commonly implemented with references or non-owning pointers.
  • Example: A Department aggregates Professor objects that may exist before or after that department.
  • Ownership rule: The aggregating class must not delete externally owned objects unless ownership is explicitly transferred.
  • Meaning: Aggregation models association and grouping, not substitutability.

B. Composition

Composition is a strong “has-a” relationship in which the part’s lifetime is controlled by the containing object.

  • Lifetime: Member objects are constructed with the whole and destroyed automatically with it.
  • Representation: It is commonly implemented through direct data members or owning smart pointers.
  • Example:
CPP
class Engine {};

class Car {
    Engine engine;  // Constructed and destroyed with Car
};
  • Construction: Engine is constructed before the Car constructor body executes.
  • Destruction: Engine is destroyed after the Car destructor body, during member destruction.
  • Contrast: Aggregation links independent objects; composition makes the part an owned component of the whole.