Team LiB
Previous Section Next Section

18.4. Nested Classes

18.4. 嵌套类

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.

可以在另一个类内部定义一个类,这样的类是嵌套类,也称为嵌套类型。嵌套类最常用于定义执行类,如第十六章QueueItem 类。

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.

嵌套类的名字在其外围类的作用域中可见,但在其他类作用域或定义外围类的作用域中不可见。嵌套类的名字将不会与另一作用域中声明的名字冲突。

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.

嵌套类可以具有与非嵌套类相同种类的成员。像任何其他类一样,嵌套类使用访问标号控制对自己成员的访问。成员可以声明为 publicprivateprotected。外围类对嵌套类的成员没有特殊访问权,并且嵌套类对其外围类的成员也没有特殊访问权。

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.

嵌套类定义了其外围类中的一个类型成员。像任何其他成员一样,外围类决定对这个类型的访问。在外围类的 public 部分定义的嵌套类定义了可在任何地方使用的类型,在外围类的 protected 部分定义的嵌套类定义了只能由外围类、友元或派生类访问的类型,在外围类的 private 部分定义的嵌套类定义了只能被外围类或其友元访问的类型。

18.4.1. A Nested-Class Implementation

18.4.1. 嵌套类的实现

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.

第十六章中实现的 Queue 类定义了一个名为 QueueItem 的伙伴执行类,QueueItem 类是私有类(它只有 private 成员)但它是在全局作用域中定义的。普通用户代码不能使用 QueueItem 类的对象:它的所有成员,包括构造函数,均为 private。但是,名字 QueueItem 是全局可见的,不能定义名为 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.

一个更好的设计可能是,将 QueueItem 类设为 Queue 类的 private 成员,那样,Queue 类(及其友元)可以使用 QueueItem,但 QueueItem 类类型对普通用户代码不可见。一旦 QueueItem 类本身为 private,我们就可以使其成员为 public 成员——只有 QueueQueue 的友元可以访问 QueueItem 类型,所以不必防止一般程序访问 QueueItem 成员。通过用保留字 struct 定义 QueueItem 使成员为 public 成员。

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.

因为 QueueItem 类是 private 成员,所以只有 Queue 类的成员和友元可以使用 QueueItem 类型。使 QueueItem 类成为 private 成员之后,就可以使 QueueItem 成员 public,这样做使我们能够删去 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.

因为 Queue 类是一个模板,它的成员也隐含地是模板。具体而言,嵌套类 QueueItem 隐含地是一个类模板。像 Queue 类中任何其他成员一样,QueueItem 的模板形参与其外围类(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.

Queue 类的每次实例化用对应于 Type 的适当模板实参产生自己的 QueueItem 类。QueueItem 类模板的实例化与外围 Queue 类模板的实例化之间的映射是一对一的。

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.

这个 QueueItem 类版本中,我们选择不在类内部定义 QueueItem 构造函数,相反,我们单独定义它。唯一复杂的是在哪里定义以及怎样命名。

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:

QueueItem 类的构造函数不是 Queue 类的成员,因此,不能将它定义在 Queue 类定义体中的任何地方,它必须与 Queue 类在同一作用域但在 Queue 类的外部定义。为了将成员定义在嵌套类定义体外部,必须记住,成员的名字在类外部是不可见的。要定义这个构造函数,必须指出,QueueItemQueue 类作用域中的嵌套类,通过用外围 Queue 类的名字限定类名 QueueItem 来做到这一点:

     // 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.

当然,QueueQueueItem 都是类模板,因此,这个构造函数也是模板。

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>.

这段代码定义了一个函数模板,以名为 Type 的单个类型形参化为形参。从右至左读函数的名字,这个函数是 QueueItem 类的构造函数,它嵌套在 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:

例如,我们可能希望将 QueueItem 类的定义放在它自己的文件中,我们可以在 Queue 类及其成员的实现文件中包含这个文件。正如可以在类定义体外部定义嵌套类的成员一样,我们也可以在外围类定义体的外部定义整个嵌套类:

     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.

为了在外围类的外部定义类体,必须用外围类的名字限定嵌套类的名字。注意,我们仍然必须在 Queue 类的定义体声明 QueueItem 类。

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.

在看到在类定义体外部定义的嵌套类的实际定义之前,该类是不完全类型(第 12.1.4 节),应用所有使用不完全类型的常规限制。



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:

如果 QueueItem 类声明了一个静态成员,它的定义也需要放在外层作用域中。假定 QueueItem 类有一个静态成员,它的定义看起来可能像下面这样:

     // 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.

嵌套类中的非静态函数具有隐含的 this 指针,指向嵌套类型的对象。嵌套类型对象只包含嵌套类型的成员,不能使用 this 指针获取外围类的成员。同样,外围类中的非静态成员函数也具有 this 指针,它指向外围类型的对象,该对象只具有外围类中定义的成员。

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:

外围类的非静态数据或函数成员的任何使用都要求通过外围类的指针、引用或对象进行。Queue 类中的 pop 函数不能直接使用 itemnext

     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.

Queue 类型的对象没有名为 itemnext 成员。Queue 类的函数成员可以使用 headtail 成员(它们是指向 QueueItem 对象的指针)来获取那些 QueueItem 成员。

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.

嵌套类可以直接引用外围类的静态成员、类型名和枚举成员(第 2.7 节),当然,引用外围类作用域之外的类型名或静态成员,需要作用域确定操作符。

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.

这样的定义,用 int 类型实例化了 Queue 模板,但没有实例化 QueueItem<int> 类型。成员 headtail 是指向 QueueItem<int> 指针,这里不需要实例化 QueueItem<int> 来定义那个类的指针。

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>.

使 QueueItem 类成为类模板 Queue 的嵌套类并不改变 QueueItem 的实例化。只有在使用 QueueItem<int> 的时候——本例中,只有当 Queue<int> 类的成员函数中对 headtail 解引用的蚨,才实例化 Queue<int> 类。

18.4.2. Name Lookup in Nested Class Scope

18.4.2. 嵌套类作用域中的名字查找

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.

对嵌套类中所用名字的名字查找(第 12.3.1 节)在普通类的名字查找之前进行,现在唯一的区别是可能要查找一个或多个外围类作用域。

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.

编译器首先处理 Outer 类成员的声明 Outer::InnerOuter::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.

将名字 Outer 作为 Inner::process 形参的使用被绑定到外围类,在看到 process 的声明时,那个类仍是不完整的,但形参是一个引用,所以这个使用是正确的。

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

数据成员 Inner::val 的声明是错误的,还没有看到 Inner2 类型。

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.

Inner2 中的声明看来没有问题——它们大多只使用内置类型 int。唯一的例外是成员函数 process,它的形参确定为不完全类型 Outer。因为其形参是一个引用,所以 Outer 为不完全类型是无关紧要的。

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.

直到看到了外围类中的其余声明之后,编译器才处理构造函数和 process 成员的定义。对 Outer 类声明的完成将函数 handle 的声明放在作用域中。

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.

当编译器查找 Inner2 类中的定义所用的名字时,Inner2 类和 Outer 类中的所有名字都在作用域中。val 的使用(出现在 val 的声明之前)是正确的:将该引用绑定到 Inner2 类中的数据成员。同样,Inner2::process 成员函数体中对 Outer 类的 handle 的使用也正确,当编译 Inner2 类的成员的时候,整个 Outer 类在作用域中。

Using the Scope Operator to Control Name Lookup
使用作用域操作符控制名字查找

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

可以使用作用域操作符访问 handle 的全局版本。

     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.

重新实现第十六章QueueQueueItem 类,使 QueueItem 成为 Queue 内部的嵌套类。

Exercise 18.33:

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

解释 Queue 设计的原来版本和嵌套类版本的优缺点。


Team LiB
Previous Section Next Section