Unit 4 - Notes

CSE202

Unit 4: Operator Overloading, Type Conversion and Inheritance

1. Operator Overloading

Operator overloading allows C++ operators to work with user-defined data types (objects) in the same way they work with primitive types (int, float, etc.). It is a type of compile-time polymorphism.

General Syntax

CPP
return_type operator symbol (arguments) {
    // ... body ...
}

Rules for Operator Overloading

  1. Existing operators cannot be created (e.g., you cannot create **).
  2. The precedence and associativity of operators cannot be changed.
  3. At least one operand must be a user-defined type.
  4. Operators that cannot be overloaded:
    • . (Member access)
    • .* (Member pointer access)
    • :: (Scope resolution)
    • ?: (Ternary operator)
    • sizeof

Unary Operator Overloading

Unary operators act on a single operand (e.g., ++, --, - negation).

  • When overloaded as a member function, it takes no arguments.
  • When overloaded as a friend function, it takes one argument.

Example: Overloading Unary Minus (-)

CPP
class Space {
    int x, y, z;
public:
    Space(int a, int b, int c) : x(a), y(b), z(c) {}
    
    // Member function
    void operator-() {
        x = -x;
        y = -y;
        z = -z;
    }
};

int main() {
    Space S(10, -20, 30);
    -S; // Activates operator-() function
}

Binary Operator Overloading

Binary operators act on two operands (e.g., +, -, *, /, >).

  • When overloaded as a member function, it takes one argument (the right-hand operand). The left-hand operand is the calling object (this).
  • When overloaded as a friend function, it takes two arguments.

Example: Overloading Binary Plus (+)

CPP
class Complex {
    float real, imag;
public:
    Complex(float r = 0, float i = 0) : real(r), imag(i) {}

    // Overloading + operator
    Complex operator+(const Complex &obj) {
        Complex temp;
        temp.real = real + obj.real;
        temp.imag = imag + obj.imag;
        return temp;
    }
};

int main() {
    Complex c1(2.5, 3.5), c2(1.6, 2.7);
    Complex c3 = c1 + c2; // Equivalent to c1.operator+(c2)
}


2. Type Conversions

C++ handles automatic conversion for built-in types (e.g., int to float). For user-defined types, we must define the conversion logic.

A. Basic Type to Class Type

This is achieved using a Constructor. The constructor must accept a single argument of the basic type.

CPP
class Time {
    int hrs, mins;
public:
    // Constructor converts int (duration in mins) to Time object
    Time(int t) {
        hrs = t / 60;
        mins = t % 60;
    }
};

int main() {
    int duration = 85;
    Time T1 = duration; // Calls constructor Time(int)
}

B. Class Type to Basic Type

This is achieved using a Casting Operator Function.

  • Syntax: operator typeName()
  • It must be a member function.
  • It must not specify a return type (the return type is inferred from the function name).
  • It should not take arguments.

CPP
class Vector {
    int magnitude;
public:
    Vector(int m) : magnitude(m) {}

    // Conversion function: Vector to int
    operator int() {
        return magnitude;
    }
};

int main() {
    Vector v1(100);
    int val = v1; // Calls operator int()
}


3. Inheritance Basics

Inheritance is the process by which objects of one class acquire the properties and behaviors of another class. It supports the concept of Reusability.

  • Base Class (Parent): The existing class.
  • Derived Class (Child): The new class created from the base class.

Syntax:

CPP
class DerivedClassName : visibility_mode BaseClassName {
    // members of derived class
};

Visibility Modes (Access Specifiers)

The visibility mode determines how the base class members are inherited into the derived class.

Mode Base: Public Members Base: Protected Members Base: Private Members
Public Public in Derived Protected in Derived Not Inherited
Protected Protected in Derived Protected in Derived Not Inherited
Private Private in Derived Private in Derived Not Inherited

Note: Private members of the base class are never directly accessible in the derived class, regardless of the inheritance mode.


4. Types of Inheritance

1. Single Inheritance

A derived class inherits from only one base class.

  • Structure: A B

2. Multilevel Inheritance

A class is derived from another derived class.

  • Structure: A B C
  • B inherits from A; C inherits from B.

3. Multiple Inheritance

A derived class inherits from more than one base class simultaneously.

  • Structure: A + B C
  • Syntax: class C : public A, public B { ... };

4. Hierarchical Inheritance

Multiple derived classes inherit from a single base class.

  • Structure: A B, A C, A D

5. Hybrid Inheritance

A combination of two or more types of inheritance (e.g., Multilevel + Multiple). This often leads to the "Diamond Problem."


5. Overriding Member Functions

Function Overriding occurs when a derived class defines a function with the exact same name and signature as a function in the base class. The derived class version "hides" the base class version.

Accessing the Overridden Function:
To access the base class function from the derived class or main, use the Scope Resolution Operator (::).

CPP
class Base {
public:
    void show() { cout << "Base"; }
};

class Derived : public Base {
public:
    void show() { cout << "Derived"; } // Overrides Base::show()
};

int main() {
    Derived d;
    d.show();       // Prints "Derived"
    d.Base::show(); // Prints "Base"
}


6. Order of Execution: Constructors and Destructors

Constructors

Constructors are executed in the order of inheritance.

  1. Base Class Constructor is called first.
  2. Derived Class Constructor is called second.

In Multiple Inheritance (Class C : A, B), Constructor A runs, then B, then C.

Passing Arguments to Base Constructor:
If the base class constructor is parameterized, the derived class must pass the arguments using an initializer list.

CPP
Derived(int x, int y) : Base(x) { 
    // Derived initialization using y
}

Destructors

Destructors are executed in the reverse order of construction.

  1. Derived Class Destructor is called first.
  2. Base Class Destructor is called second.

7. Resolving Ambiguities in Inheritance

Ambiguity arises in Multiple Inheritance when two base classes have a function with the same name. The compiler cannot distinguish which function to call.

Example:

CPP
class A { public: void display() { ... } };
class B { public: void display() { ... } };
class C : public A, public B { ... };

// In main:
C obj;
obj.display(); // Error: Ambiguous

Solution: Use the scope resolution operator to specify the class.

CPP
obj.A::display(); // Calls A's version
obj.B::display(); // Calls B's version


8. Virtual Base Class (The Diamond Problem)

The Problem:
In Hybrid Inheritance (e.g., A B, A C, B+C D), class D inherits from both B and C. Since both B and C inherit from A, class D ends up with two copies of class A's data members. This causes ambiguity and memory waste.

The Solution:
Make the inheritance of the common ancestor virtual. This ensures only one copy of the base class member variables is inherited by the grandchild class.

Syntax:

CPP
class A { public: int x; };
class B : virtual public A { ... }; // Virtual inheritance
class C : public virtual A { ... }; // Virtual inheritance (order of keywords doesn't matter)
class D : public B, public C { ... }; 

int main() {
    D obj;
    obj.x = 10; // Valid (no ambiguity because only one 'x' exists)
}


9. Aggregation and Composition

These concepts represent a "Has-A" relationship, as opposed to Inheritance which is an "Is-A" relationship. They involve embedding objects of one class into another.

Composition (Strong Association)

  • Concept: The contained object cannot exist without the container object. If the parent is destroyed, the child is destroyed.
  • Example: A House has a Room. A room cannot exist without the house.
  • Implementation: Usually implemented using direct object members.
    CPP
    class Room { ... };
    class House {
        Room r1; // House creates and manages Room directly
    };
    

Aggregation (Weak Association)

  • Concept: The contained object can exist independently of the container object. The parent has a reference to the child, but does not exclusively own it.
  • Example: A Car has a Driver. The driver can exist without the car.
  • Implementation: Usually implemented using pointers or references.
    CPP
    class Driver { ... };
    class Car {
        Driver* d1; // Car points to a Driver
    public:
        void setDriver(Driver* d) { d1 = d; }
    };