Consider the following short program:
There’s nothing wrong with this program. But because enum class FruitType is meant to be
used in conjunction with the Fruit class, having it exist independently of the class leaves
us to infer how they are connected.
Nested types (member types)
So far, we’ve seen class types with two different kinds of members: data members and member functions.
Our Fruit class in the example above has both of these.
Class types support another kind of member: nested types (also called member types). To create a nested type, you simply define the type inside the class, under the appropriate access specifier.
Here’s the same program as above, rewritten to use a nested type defined inside the Fruit
class:
There are a few things worth pointing out here.
First, note that FruitType is now defined inside the class, where it has been renamed
Type for reasons that we will discuss shortly.
Second, nested type Type has been defined at the top of the class. Nested type names must be
fully defined before they can be used, so they are usually defined first.
Best practice
Define any nested types at the top of your class type.
Third, nested types follow normal access rules. Type is defined under the
public access specifier, so that the type name and enumerators can be directly accessed by
the public.
Fourth, class types act as a scope region for names declared within, just as namespaces do. Therefore the
fully qualified name of Type is Fruit::Type, and the fully qualified name of
the apple enumerator is Fruit::apple.
Within the members of the class, we do not need to use the fully qualified name. For example, in member
function isCherry() we access the cherry enumerator without the
Fruit:: scope qualifier.
Outside the class, we must use the fully qualified name (e.g. Fruit::apple). We renamed
FruitType to Type so we can access it as Fruit::Type (rather than
the more redundant Fruit::FruitType).
Finally, we changed our enumerated type from scoped to unscoped. Since the class itself is now acting as
a scope region, it’s somewhat redundant to use a scoped enumerator as well. Changing to an unscoped enum
means we can access enumerators as Fruit::apple rather than the longer
Fruit::Type::apple we’d have to use if the enumerator were scoped.
Nested typedefs and type aliases
Class types can also contain nested typedefs or type aliases:
This prints:
John has id: 1
Note that inside the class we can just use IDType, but outside the class we must use the
fully qualified name Employee::IDType.
We discuss the benefits of type aliases in lesson 10.7 -- Typedefs and type
aliases, and they serve the same purpose here. It is very common for classes in the C++ standard
library to make use of nested typedefs. As of the time of writing, std::string defines ten
nested typedefs!
Nested classes and access to outer class members
It is fairly uncommon for classes to have other classes as a nested type, but it is possible. In C++, a
nested class does not have access to the this pointer of the outer (containing) class, so
nested classes can not directly access the members of the outer class. This is because a nested class
can be instantiated independently of the outer class (and in such a case, there would be no outer class
members to access!)
However, because nested classes are members of the outer class, they can access any private members of the outer class that are in scope.
Let’s illustrate with an example:
This prints:
John has id: 1
There is one case where nested classes are more commonly used. 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. We’ll cover iterators in a future chapter.
Nested types and forward declarations
A nested type can be forward declared within the class that encloses it. The nested type can then be defined later, either within the enclosing class, or outside of it. For example:
However, a nested type cannot be forward declared prior to the definition of the enclosing class.
While you can forward declare a nested type after the definition of the enclosing class, since the enclosing class will already contain a declaration for the nested type, doing so is redundant.