Skip to content

19.5. Nested Classes

A class can be defined within another class. Such a class is a nested class, also referred to as a nested type. Nested classes are most often used to define implementation classes, such as the QueryResult class we used in our text query example (§ 12.3, p. 484).

INFO

Exercises Section 19.4.3

Exercise 19.18: Write a function that uses count_if to count how many empty strings there are in a given vector.

Exercise 19.19: Write a function that takes a vector<Sales_data> and finds the first element whose average price is greater than some given amount.

Nested classes are independent classes and are largely unrelated to their enclosing class. In particular, objects of the enclosing and nested classes are independent from each other. An object of the nested type does not have members defined by the enclosing class. Similarly, an object of the enclosing class does not have members defined by the nested class.

The name of a nested class is visible within its enclosing class scope but not outside the class. Like any other nested name, the name of a nested class will not collide with the use of that name in another scope.

A nested class can have the same kinds of members as a nonnested class. Just like any other class, a nested class controls access to its own members using access specifiers. The enclosing class has no special access to the members of a nested class, and the nested class has no special access to members of its enclosing class.

A nested class defines a type member in its enclosing class. As with any other member, the enclosing class determines access to this type. A nested class defined in the public part of the enclosing class defines a type that may be used anywhere. A nested class defined in the protected section defines a type that is accessible only by the enclosing class, its friends, and its derived classes. A private nested class defines a type that is accessible only to the members and friends of the enclosing class.

Declaring a Nested Class

The TextQuery class from § 12.3.2 (p. 487) defined a companion class named QueryResult. The QueryResult class is tightly coupled to our TextQuery class. It would make little sense to use QueryResult for any other purpose than to represent the results of a query operation on a TextQuery object. To reflect this tight coupling, we’ll make QueryResult a member of TextQuery.

c++
class TextQuery {
public:
    class QueryResult; // nested class to be defined later
    // other members as in § 12.3.2 (p. 487)
};

We need to make only one change to our original TextQuery class—we declare our intention to define QueryResult as a nested class. Because QueryResult is a type member (§ 7.4.1, p. 284), we must declare QueryResult before we use it. In particular, we must declare QueryResult before we use it as the return type for the query member. The remaining members of our original class are unchanged.

Defining a Nested Class outside of the Enclosing Class

Inside TextQuery we declared QueryResult but did not define it. As with member functions, nested classes must be declared inside the class but can be defined either inside or outside the class.

When we define a nested class outside its enclosing class, we must qualify the name of the nested class by the name of its enclosing class:

c++
// we're defining the QueryResult class that is a member of class TextQuery
class TextQuery::QueryResult {
    // in class scope, we don't have to qualify the name of the QueryResult parameters
    friend std::ostream&
           print(std::ostream&, const QueryResult&);
public:
    // no need to define QueryResult::line_no; a nested class can use a member
    // of its enclosing class without needing to qualify the member's name
    QueryResult(std::string,
                std::shared_ptr<std::set<line_no>>,
                std::shared_ptr<std::vector<std::string>>);
    // other members as in § 12.3.2 (p. 487)
};

The only change we made compared to our original class is that we no longer define a line_no member in QueryResult. The members of QueryResult can access that name directly from TextQuery, so there is no need to define it again.

WARNING

Until the actual definition of a nested class that is defined outside the class body is seen, that class is an incomplete type (§ 7.3.3, p. 278).

Defining the Members of a Nested Class

In this version, we did not define the QueryResult constructor inside the class body. To define the constructor, we must indicate that QueryResult is nested within the scope of TextQuery. We do so by qualifying the nested class name with the name of its enclosing class:

c++
// defining the member named QueryResult for the class named QueryResult
// that is nested inside the class TextQuery
TextQuery::QueryResult::QueryResult(string s,
                shared_ptr<set<line_no>> p,
                shared_ptr<vector<string>> f):
        sought(s), lines(p), file(f) { }

Reading the name of the function from right to left, we see that we are defining the constructor for class QueryResult, which is nested in the scope of class TextQuery. The code itself just stores the given arguments in the data members and has no further work to do.

Nested-Class static Member Definitions

If QueryResult had declared a static member, its definition would appear outside the scope of the TextQuery. For example, assuming QueryResult had a static member, its definition would look something like

c++
// defines an int static member of QueryResult
// which is a class nested inside TextQuery
int TextQuery::QueryResult::static_mem = 1024;

Name Lookup in Nested Class Scope

Normal rules apply for name lookup (§ 7.4.1, p. 283) inside a nested class. Of course, because a nested class is a nested scope, the nested class has additional enclosing class scopes to search. This nesting of scopes explains why we didn’t define line_no inside the nested version of QueryResult. Our original QueryResult class defined this member so that its own members could avoid having to write TextQuery::line_no. Having nested the definition of our results class inside TextQuery, we no longer need this typedef. The nested QueryResult class can access line_no without specifying that line_no is defined in TextQuery.

As we’ve seen, a nested class is a type member of its enclosing class. Members of the enclosing class can use the name of a nested class the same way it can use any other type member. Because QueryResult is nested inside TextQuery, the query member of TextQuery can refer to the name QueryResult directly:

c++
// return type must indicate that QueryResult is now a nested class
TextQuery::QueryResult
TextQuery::query(const string &sought) const
{
    // we'll return a pointer to this set if we don't find sought
    static shared_ptr<set<line_no>> nodata(new set<line_no>);
    // use find and not a subscript to avoid adding words to wm!
    auto loc = wm.find(sought);
    if (loc == wm.end())
        return QueryResult(sought, nodata, file);  // not found
    else
        return QueryResult(sought, loc->second, file);
}

As usual, the return type is not yet in the scope of the class (§ 7.4, p. 282), so we start by noting that our function returns a TextQuery::QueryResult value. However, inside the body of the function, we can refer to QueryResult directly, as we do in the return statements.

The Nested and Enclosing Classes Are Independent

Although a nested class is defined in the scope of its enclosing class, it is important to understand that there is no connection between the objects of an enclosing class and objects of its nested classe(s). A nested-type object contains only the members defined inside the nested type. Similarly, an object of the enclosing class has only those members that are defined by the enclosing class. It does not contain the data members of any nested classes.

More concretely, the second return statement in TextQuery::query

c++
return QueryResult(sought, loc->second, file);

uses data members of the TextQuery object on which query was run to initialize a QueryResult object. We have to use these members to construct the QueryResult object we return because a QueryResult object does not contain the members of its enclosing class.

INFO

Exercises Section 19.5

Exercise 19.20: Nest your QueryResult class inside TextQuery and rerun the programs you wrote to use TextQuery in § 12.3.2 (p. 490).