#121 – Classes inside classes: Nested classes is a thing

Just as you can have structs contain other structs, similarly, you can have classes contain other classes. This is called a nested class.

A helper class that belongs to one class

Consider a Playlist class.

We want a small helper that prints a playlist summary. That helper is not useful on its own. It only makes sense when paired with Playlist, so we can nest it inside the class.

#include <iostream>
#include <string>
#include <string_view>
#include <vector>

class Playlist {
public:
    class SummaryPrinter {
    public:
        void print(const Playlist& playlist) const{
            std::cout << "Playlist: " << playlist.m_name << '\n';
            std::cout << "Tracks: " << playlist.m_tracks.size() << '\n';

            for (const std::string& track : playlist.m_tracks) {
                std::cout << "- " << track << '\n';
            }
        }
    };

private:
    std::string m_name {};
    std::vector<std::string> m_tracks {};

public:
    explicit Playlist(std::string_view name)
        : m_name { name }
    {
    }

    void addTrack(std::string_view track){
        m_tracks.emplace_back(track);
    }
};

int main(){
    Playlist playlist { "Morning Focus" };

    playlist.addTrack("Low Light");
    playlist.addTrack("Deep Work");
    playlist.addTrack("Clean Signal");

    Playlist::SummaryPrinter printer {};
    printer.print(playlist);
}

Terminal output:
Playlist: Morning Focus
Tracks: 3
- Low Light
- Deep Work
- Clean Signal

Because the nested class SummaryPrinter is inside Playlist, its full name outside the class is:

Playlist::SummaryPrinter

That scope communicates the relationship clearly: this printer belongs conceptually to Playlist.

Nested classes and private access

SummaryPrinter can access private members of Playlist:

playlist.m_name
playlist.m_tracks

Those members are private:

private:
    std::string m_name {};
    std::vector<std::string> m_tracks {};

This works because a nested class is a member of the outer class. Like other members, it can access names the outer class can access.

However, there is an important distinction.

A nested class does not automatically have a this pointer for an outer Playlist object.

That is why print() takes a Playlist parameter:

void print(const Playlist& playlist) const

The nested class can access private members, but it still needs an actual Playlist object to access non-static data.

Standard-library style: iterator member types

In the standard library, most iterator classes are implemented as nested classes of the container they are designed to iterate over. For example, std::string::iterator is implemented as a nested class of std::string.

Containers expose associated types through the class scope:

std::vector<int>::value_type
std::vector<int>::size_type
std::vector<int>::iterator
std::string::iterator

Example:

#include <iostream>
#include <string>
#include <vector>

int main(){
    std::vector<int> scores { 10, 20, 30 };

    std::vector<int>::value_type score { 95 };
    std::vector<int>::size_type count { scores.size() };

    std::cout << "Score type value: " << score << '\n';
    std::cout << "Vector count: " << count << '\n';

    std::string name { "Ada" };
    std::string::iterator it { name.begin() };

    *it = 'I';

    std::cout << name << '\\n';
}

Terminal output:
Score type value: 95
Vector count: 3
Ida

The important part is this: std::string::iterator

The iterator type is accessed through the class scope because it is associated with std::string.

Likewise: std::vector<int>::iterator means “the iterator type for this particular vector specialization.”

In Summary

  • A nested class is a class declared inside another class.
  • A nested class name is accessed with the scope resolution operator, Playlist::SummaryPrinter
  • A nested class has access to the outer class’s private names, but it does hold a this* to it.