Section 18.4. Nested Classes


18.4. 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 QueueItem class from Chapter 16.

Nested classes are independent classes and are largely unrelated to their enclosing class. Objects of the enclosing and nested classes are, therefore, independent from one another. 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 in its enclosing class scope but not in other class scopes or in the scope in which the enclosing class is defined. The name of a nested class will not collide with the same name declared in another scope.

Exercises Section 18.3.2

Exercise 18.25:

What is the type of the Screen class members screen and cursor?

Exercise 18.26:

Define a pointer to member that could point to the cursor member of class Screen. Fetch the value of Screen::cursor through that pointer.

Exercise 18.27:

Define a typedef for each distinct type of Screen member function.

Exercise 18.28:

Pointers to members may also be declared as class data members. Modify the Screen class definition to contain a pointer to a Screen member function of the same type as home and end.

Exercise 18.29:

Write a Screen constructor that takes a parameter of type pointer to Screen member function whose parameter list and return type are the same as those for the member functions home and end.

Exercise 18.30:

Provide a default argument for this parameter. Use this parameter to initialize the data member introduced in the previous exercise.

Exercise 18.31:

Provide a Screen member function to set this member.


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 labels. Members may be declared public, private, or protected. 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, or its derived classes. A private nested class defines a type that is accessible only to the members of the enclosing class or its friends.

18.4.1. A Nested-Class Implementation

The Queue class that we implemented in Chapter 16 defined a companion implementation class named QueueItem. That class was a private classit had only private membersbut it was defined at the global scope. General user code cannot use objects of class QueueItem: All its members, including constructors, are private. However, the name QueueItem is visible globally. We cannot define our own type or other entity named QueueItem.

A better design would be to make the QueueItem class a private member of class Queue. That way, the Queue class (and its friends) could use QueueItem, but the QueueItem class type would not be visible to general user code. Once the class itself is private, we can make its members publiconly Queue or the friends of Queue can access the QueueItem type, so there is no need to protect its members from general program access. We make the members public by defining QueueItem using the keyword struct.

Our new design looks like:

      template <class Type> class Queue {          // interface functions to Queue are unchanged      private:          // public members are ok: QueueItem is a private member of Queue          // only Queue and its friends may access the members of QueueItem          struct QueueItem {              QueueItem(const Type &);              Type item;            // value stored in this element              QueueItem *next;      // pointer to next element in the Queue          };          QueueItem *head;      // pointer to first element in Queue          QueueItem *tail;      // pointer to last element in Queue      }; 

Because the class is a private member, only members and friends of the Queue class can use the QueueItem type. Having made the class a private member, we can make the QueueItem members public. Doing so lets us eliminate the friend declarations in QueueItem.

Classes Nested Inside a Class Template Are Templates

Because Queue is a template, its members are implicitly templates as well. In particular, the nested class QueueItem is implicitly a class template. Again, like any other member in Queue, the template parameter for QueueItem is the same as the template parameter of its enclosing class: class Queue.

Each instantiation of Queue generates its own QueueItem class with the appropriate template argument for Type. The mapping between an instantiation for the QueueItem class template and an instantiation of the enclosing Queue class template is one to one.

Defining the Members of a Nested Class

In this version of QueueItem, we chose not to define the QueueItem constructor inside the class. Instead, we'll define it separately. The only trick is where to define it and how to name it.

A nested-class member defined outside its own class must be defined in the same scope as the scope in which the enclosing class is defined. A member of a nested class defined outside its own class may not be defined inside the enclosing class itself. A member of a nested class is not a member of the enclosing class.



The constructor for QueueItem is not a member of class Queue. Therefore, it cannot be defined elsewhere in the body of class Queue. It must be defined at the same scope as the Queue class but outside that class. To define a member outside the nested-class body, we must remember that its name is not visible outside the class. To define the constructor, we must indicate that QueueItem is a nested class within the scope of class Queue. We do so by qualifying the class name QueueItem with the name of its enclosing class Queue:

      // defines the QueueItem constructor      // for class QueueItem nested inside class Queue<Type>      template <class Type>      Queue<Type>::QueueItem::QueueItem(const Type &t):                               item(t), next(0) { } 

Of course, both Queue and QueueItem are class templates. The constructor, therefore, is also a template.

This code defines a function template, parameterized by a single type parameter named Type. Reading the name of the function from right to left, this function is the constructor for class QueueItem, which is a nested in the scope of class Queue<Type>.

Defining the Nested Class Outside the Enclosing Class

Nested classes often support implementation details for the enclosing class. We might want to prevent users of the enclosing class from seeing the code that implements the nested class.

For example, we might want to put the definition of class QueueItem in its own file, which we would include in those files containing the implementation of the Queue class and its members. Just as we can define the members of a nested class outside the class body, we can define the entire class outside the body of the enclosing class:

      template <class Type> class Queue {          // interface functions to Queue are unchanged      private:          struct QueueItem; // forward declaration of nested type QueueItem          QueueItem *head;  // pointer to first element in Queue          QueueItem *tail;  // pointer to last element in Queue      };      template <class Type>      struct Queue<Type>::QueueItem {          QueueItem(const Type &t): item(t), next(0) { }          Type item;        // value stored in this element          QueueItem *next; // pointer to next element in the Queue      }; 

To define the class body outside its enclosing class, we must qualify the name of the nested class by the name of its enclosing class. Note that we must still declare QueueItem in the body of class Queue.

A nested class also can be declared and then later defined in the body of the enclosing class. As with other forward declarations, a forward declaration of a nested class allows for nested classes that have members that refer to one another.

Until the actual definition of a nested class that is defined outside the class body is seen, that class is an incomplete type (Section 12.1.4, p. 437). All the normal retrictions on using an incomplete type apply.



Nested-Class Static Member Definitions

If QueueItem had declared a static member, its definition would also need to be defined in the outer scope. Assuming QueueItem had a static member, its definition would look somthing like:

      // defines an int static member of QueueItem,      // which is a type nested inside Queue<Type>      template <class Type>      int Queue<Type>::QueueItem::static_mem = 1024; 

Using Members of the Enclosing Class

There is no connection between the objects of an enclosing scope and objects of its nested type(s).



Nonstatic functions in the nested class have an implicit this pointer that points to an object of the nested type. A nested-type object contains only the members of the nested type. The this pointer may not be used to fetch members of the enclosing class. Similarly, the nonstatic member functions in the enclosing class have a this pointer that points to an object of the enclosing type. That object has only the members defined in the enclosing class.

Any use of a nonstatic data or function member of the enclosing class requires that it be done through a pointer, reference, or object of the enclosing class. The pop function in class Queue may not use item or next directly:

      template <class Type>      void Queue<Type>::pop()      {           // pop is unchecked: popping off an empty Queue is undefined           QueueItem* p = head;        // keep pointer to head so can delete it           head = head->next;          // head now points to next element           delete p;                   // delete old head element      } 

Objects of type Queue do not have members named item or next. Function members of Queue can use the head and tail members, which are pointers to QueueItem objects, to fetch those QueueItem members.

Using Static or Other Type Members

A nested class may refer to the static members, type names, and enumerators (Section 2.7, p. 62) of the enclosing class directly. Of course, referring to a type name or static member outside the scope of the enclosing class requires the scope-resolution operator.

Instantiation for Nested Templates

A nested class of a class template is not instantiated automatically when the enclosing class template is instantiated. Like any member function, the nested class is instantiated only if it is itself used in a context that requires a complete class type. For example, a definition such as

      Queue<int> qi; // instantiates Queue<int> but not QueueItem<int> 

instantiates the template Queue with type int but does not yet instantiate the type QueueItem<int>. The Queue members head and tail are pointers to QueueItem<int>. There is no need to instantiate QueueItem<int> to define pointers to that class.

Making QueueItem a nested class of the class template Queue does not change the instantiation of QueueItem. The QueueItem<int> class will be instantiated only when QueueItem is usedin this case, only when head or tail is dereferenced from a member function of class Queue<int>.

18.4.2. Name Lookup in Nested Class Scope

Name lookup (Section 12.3.1, p. 447) for names used in a nested class proceeds in the same manner as for a normal class, the only difference being that now there may be one or more enclosing class scopes to search.

When processing the declarations of the class members, any name used must appear prior to its use. When processing definitions, the entire nested and enclosing class(es) are in scope.



As an example of name lookup in a nested class, consider the following class declarations:

      class Outer {      public:          struct Inner {              // ok: reference to incomplete class              void process(const Outer&);              Inner2 val; // error: Outer::Inner2 not in scope          };          class Inner2 {          public:              // ok: Inner2::val used in definition              Inner2(int i = 0): val(i) { }              // ok: definition of process compiled after enclosing class is complete              void process(const Outer &out) { out.handle(); }          private:              int val;          };          void handle() const; // member of class Outer      }; 

The compiler first processes the declarations of the members of classes Outer, Outer::Inner, and Outer::Inner2.

The use of the name Outer as a parameter to Inner::process is bound to the enclosing class. That class is still incomplete when the declaration of process is seen, but the parameter is a reference, so this usage is okay.

The declaration of the data member Inner::val is an error. The type Inner2 has not yet been seen.

The declarations in Inner2 pose no problemsmostly they just use the built-in type int. The only exception is the process member function. Its parameter resolves to the incomplete type Outer. Because the parameter is a reference, the fact that Outer is an incomplete type doesn't matter.

The definitions of the constructor and process member are not processed by the compiler until the remaining declarations in the enclosing class have been seen. Completing the declarations of class Outer puts the declaration of the function handle in scope.

When the compiler looks up the names used in the definitions in class Inner2, all the names in class Inner2 and class Outer are in scope. The use of val, which appears before the declaration of val, is okay: That reference is bound to the data member in class Inner2. Similarly, the use of handle from class Outer in the body of the Inner2::process member is okay. The entire Outer class is in scope when the members of class Inner2 are compiled.

Using the Scope Operator to Control Name Lookup

The global version of handle can be accessed using the scope operator:

      class Inner2 {      public:          // ...          // ok: programmer explicitly specifies which handle to call          void process(const Outer &out) { ::handle(out); }      }; 

Exercises Section 18.4.2

Exercise 18.32:

Reimplement the Queue and QueueItem classes from Chapter 16 making QueueItem a nested class inside Queue.

Exercise 18.33:

Explain the pros and cons of the original and the nested-class version of the Queue design.




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net