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 string
s 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
.
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:
// 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:
// 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
// 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:
// 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
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.