CLOSE

In the previous unit, we covered structs and discussed how they are great for bundling multiple member variables into a single object that can be initialized and passed around as a unit. In other words, structs provide a convenient package for storing package and moving related data values.

Consider the following struct:

#include <iostream>

struct Date
{
    int day{};
    int month{};
    int year{};
};

void printDate(const Date& date)
{
    std::cout << date.day << '/' << date.month << '/' << date.year; // assume DMY format
}

int main()
{
    Date date{ 19, 11, 23 }; // initialize using aggregate initialization
    printDate(date);        // can pass entire struct to function

    return 0;
}

In the above example, we create a Date object and then pass it to a function that prints the date. This program prints:

19/11/23

The class invariant problem

Perhaps the biggest difficulty with structs is that they do not provide an effective way to document and enforce class invariants. we defined an invariant as , “a condition that must be true while some component is executing”.

Introduction to Classes

Just like structs, a class is a program-defined compound type that can have many member variables with different types.

Defining a class

Because a class is a program-defined data type, it must be defined before it can be used. Classes are defined similarly to structs, excepts we use the class keyword instead of struct. For example, here is a definition for a simple employee class:

class Employee
{
    int m_id {};
    int m_age {};
    double m_wage {};
};

Inner Classes

When building complex software systems, grouping related functionality logically is crucial for maintainability, readability, and scalability. One such organizational feature provided by C++ is the inner class, also known as a nested class.

What is an Inner Class in C++?

An inner class in C++ is a class defined inside the scope of another class. It's a way to logically associate one class with another, helping encapsulate behavior and reduce naming clutter.

class Outer {
public:
    class Inner {
    public:
        void display() {
            std::cout << "Inside Inner class" << std::endl;
        }
    };

    void show() {
        std::cout << "Inside Outer class" << std::endl;
    }
};

How to Use an Inner Class

To use an inner class, you must refer it with the scope resolution operator ::.

int main() {
    Outer::Inner obj; // Scope resolution
    obj.display();

    Outer o;
    o.show();

    return 0;
}

Characteristics of Inner Classes

FeatureBehavior
🔒 Access to Outer Class Members❌ Inner class cannot directly access outer class's non-static members.
🧭 ScopeInner class is scoped within the outer class.
🔐 Access ModifiersInner class can be public, private, or protected.
📛 Name ResolutionMust use Outer::Inner to refer to the class.
👁 FriendshipInner class can be declared as a friend to access private members of the outer class.

Access Limitation: No Implicit Link to Outer Class

Inner classes don't automatically access the outer class's members:

class Outer {
    int secret = 42;
public:
    class Inner {
    public:
        void accessOuter() {
            // std::cout << secret; ❌ Compiler error
        }
    };
};

To access secret, the inner class must use an object or pointer of the outer class.

class Outer {
    int secret = 42;
public:
    class Inner {
    public:
        void accessOuter(const Outer& outer) {
            std::cout << "Secret: " << outer.secret << std::endl;
        }
    };
};

You can define and instantiate inner classes like this:

Outer::Inner innerObj;
innerObj.accessOuter(outerInstance);

Use Cases

1️⃣ Tree Data Structures

#include <iostream>
using namespace std;

class BinaryTree {
private:
    // Inner class representing a node of the tree
    class Node {
    public:
        int data;
        Node* left;
        Node* right;

        Node(int value) : data(value), left(nullptr), right(nullptr) {}
    };

    Node* root;

    // Helper function for recursive traversal
    void inorderTraversal(Node* node) {
        if (!node) return;
        inorderTraversal(node->left);
        cout << node->data << " ";
        inorderTraversal(node->right);
    }

public:
    BinaryTree() : root(nullptr) {}

    void insert(int value) {
        root = insertRec(root, value);
    }

    void printInorder() {
        inorderTraversal(root);
        cout << endl;
    }

private:
    // Recursive helper for insertion
    Node* insertRec(Node* node, int value) {
        if (!node) return new Node(value);
        if (value < node->data)
            node->left = insertRec(node->left, value);
        else
            node->right = insertRec(node->right, value);
        return node;
    }
};

// Usage
int main() {
    BinaryTree tree;
    tree.insert(10);
    tree.insert(5);
    tree.insert(15);

    cout << "Inorder Traversal: ";
    tree.printInorder();  // Output: 5 10 15

    return 0;
}

2️⃣ Linked List with Node as an Inner Class

#include <iostream>
using namespace std;

class LinkedList {
private:
    // Inner class representing a node of the linked list
    class Node {
    public:
        int data;
        Node* next;

        Node(int value) : data(value), next(nullptr) {}
    };

    Node* head;

public:
    LinkedList() : head(nullptr) {}

    void insertAtEnd(int value) {
        Node* newNode = new Node(value);
        if (!head) {
            head = newNode;
            return;
        }

        Node* temp = head;
        while (temp->next) {
            temp = temp->next;
        }

        temp->next = newNode;
    }

    void printList() const {
        Node* temp = head;
        while (temp) {
            cout << temp->data << " -> ";
            temp = temp->next;
        }
        cout << "NULL" << endl;
    }
};

// Usage
int main() {
    LinkedList list;
    list.insertAtEnd(10);
    list.insertAtEnd(20);
    list.insertAtEnd(30);

    cout << "Linked List: ";
    list.printList();  // Output: 10 -> 20 -> 30 -> NULL

    return 0;
}

3️⃣ Helper/Utility Classes

Sometimes you want a small class used only internally within another class. Inner classes make that possible without polluting the global namespace.

Static Nested Class

Anonymous Inner Class