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.