Team LiB
Previous Section Next Section

17.3. Multiple and Virtual Inheritance

17.3. 多重继承与虚继承

Most C++ applications use public inheritance from a single base class. In some cases, however, single inheritance is inadequate, either because it fails to model the problem domain or the model it imposes is unnecessarily complex.

大多数应用程序使用单个基类的公用继承,但是,在某些情况下,单继承是不够用的,因为可能无法为问题域建模,或者会对模型带来不必要的复杂性。

In these cases, multiple inheritance may model the application more directly. Multiple inheritance is the ability to derive a class from more than one immediate base class. A multiply derived class inherits the properties of all its parents. Although simple in concept, the details of intertwining multiple base classes can present tricky design-level and implementation-level problems.

在这些情况下,多重继承可以更直接地为应用程序建模。多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性。尽管概念简单,缠绕多个基类的细节可能会带来错综复杂的设计问题或实现问题。

17.3.1. Multiple Inheritance

17.3.1. 多重继承

This section uses a pedagogical example of a zoo animal hierarchy. Our zoo animals exist at different levels of abstraction. There are the individual animals, distinguished by their names, such as Ling-ling, Mowgli, and Balou. Each animal belongs to a species; Ling-Ling, for example, is a giant panda. Species, in turn, are members of families. A giant panda is a member of the bear family. Each family, in turn, is a member of the animal kingdomin this case, the more limited kingdom of a particular zoo.

本节使用动物园动物层次的一个教学例子。动物园动物在不同抽象级别存在,有个体的动物,由名字区分,如 Ling-lineMowgliBalou;每个动物属于一个特种,例如,Ling-Ling 是一个大熊猫;物种又是科的成员,大熊猫是熊科的成员;每个科又是动物界的成员——在这个例子中,比较受限的是一个特定的动物园。

Each level of abstraction contains data and operations that support a wider category of users. We'll define an abstract ZooAnimal class to hold information that is common to all the zoo animals and provides the public interface. The Bear class will contain information that is unique to the Bear family, and so on.

每个抽象级别包含支持广泛用户的数据和操作。我们将定义一个抽象 ZooAnimal 类保存所有动物园动物公共信息并提供公用接口,Bear 类将包含 Bear 科的独特信息,以此类推。

In addition to the actual zoo-animal classes, there are auxiliary classes that encapsulate various abstractions such as endangered animals. In our implementation of a Panda class, for example, a Panda is multiply derived from Bear and Endangered.

除了实际的动物园动物类的之外,还有一些辅助类封装不同的抽象,如濒临灭绝的动物。玿,在 Panda 类的实现中,Panda 同时从 BearEndangered 派生。

Defining Multiple Classes
定义多个类

To support multiple inheritance, the derivation list

为了支持多重继承,扩充派生列表

    class Bear : public ZooAnimal {
    };

is extended to support a comma-separated list of base classes:

以支持由逗号分隔的基类列表:

    class Panda : public Bear, public Endangered {
    };

The derived class specifies (either explicitly or implicitly) the access level public, protected, or private for each of its base classes. As with single inheritance, a class may be used as a base class under multiple inheritance only after it has been defined. There is no language-imposed limit on the number of base classes from which a class can be derived. A base class may appear only once in a given derivation list.

派生类为每个基类(显式或隐式地)指定了访问级别——publicprotectedprivate。像单继承一样,只有在定义之后,类才可以用作多重继承的基类。对于类可以继承的基类的数目,没有语言强加强加的限制,但在一个给定派生列表中,一个基类只能出现一次。

Multiply Derived Classes Inherit State from Each Base Class
多重继承的派生类从每个基类中继承状态

Under multiple inheritance, objects of a derived class contain a base-class subobject (Section 15.2.3, p. 565) for each of its base classes. When we write

在多重继承下,派生类的对象包含每个基类的基类子对象(第 15.2.3 节)。当我们编写

Panda ying_yang("ying_yang");

the object ying_yang is composed of a Bear class subobject (which itself contains a ZooAnimal base-class subobject), an Endangered class subobject, and the nonstatic data members, if any, declared within the Panda class (see Figure 17.2).

的时候,对象 ying_yang 包含一个 Bear 类子对象(Bear 类子对象本身包含一个 ZooAnimal 基类子对象)、一个 Endangered 类子对象以及 Panda 类中声明的非 static 数据成员(如果有的话),见图 17.2

Figure 17.2. Multiple Inheritance Panda Hierarchy
17.2. 多重继承的 Panda 层次


Derived Constructors Initialize All Base Classes
派生类构造函数初始化所有基类

Constructing an object of derived type involves constructing and initializing all its base subobjects. As is the case for inheriting from a single base class (Section 15.4.1, p. 580), derived constructors may pass values to zero or more of their base classes in the constructor initializer:

构造派生类型的对象包括构造和初始化宾所有基类子对象。像继承单个基类(第 15.4.1 节)的情况一样,派生类的构造函数可以在构造函数初始化式中给零个或多个基类传递值:

    // explicitly initialize both base classes
    Panda::Panda(std::string name, bool onExhibit)
          : Bear(name, onExhibit, "Panda"),
            Endangered(Endangered::critical) { }
    // implicitly use Bear default constructor to initialize the Bear subobject
    Panda::Panda()
          : Endangered(Endangered::critical) { }

Order of Construction
构造的次序

The constructor initializer controls only the values that are used to initialize the base classes, not the order in which the base classes are constructed. The base-class constructors are invoked in the order in which they appear in the class derivation list. For Panda, the order of base-class initialization is:

构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中的出现次序调用。对 Panda 而言,基类初始化的次序是:

  1. ZooAnimal, the ultimate base class up the hierarchy from Panda's immediate base class Bear

    ZooAnimal,从 Panda 的直接基类 Bear 沿层次向上的最终基类。

  2. Bear, the first immediate base class

    Bear,第一个直接基类。

  3. Endangered, the second immediate base, which itself has no base class

    Endangered,第二个直接基类,它本身没有基类。

  4. Panda; the members of Panda itself are initialized, and then the body of its constructor is run.

    Panda,初始化 Panda 本身的成员,然后运行它的构造函数的函数体。

The order of constructor invocation is not affected by whether the base class appears in the constructor initializer list or the order in which base classes appear within that list.

构造函数调用次序既不受构造函数初始化列表中出现的基类的影响,也不受基类在构造函数初始化列表中的出现次序的影响。



For example, in Panda's default constructor, the Bear default constructor is invoked implicitly; it does not appear in the constructor initializer list. Even so, Bear's default constructor is invoked prior to the explicitly listed constructor of Endangered.

例如,在 Panda 类的默认构造函数中,隐式调用 Bear 类的默认构造函数,它不出现在构造函数初始化列表中,虽然如此,仍在显式列出的 Endangered 类构造函数之前调用 Bear 类的默认构造函数。

Order of Destruction
析构的次序

Destructors are always invoked in the reverse order from which the constructors are run. In our example, the order in which the destructors are called is ~Panda, ~Endangered, ~Bear, ~ZooAnimal.

总是按构造函数运行的逆序调用析构函数。在我们的例子中,调用析构函数的次序是 ~Panda, ~Endangered, ~Bear, ~ZooAnimal

Exercises Section 17.3.1

Exercise 17.23:

Which, if any, of the following declarations are in error. Explain why.

如果有,下面哪些声明是错误的。解释为什么。

    (a) class CADVehicle : public CAD, Vehicle { ... };
    (b) class DoublyLinkedList:
              public List, public List { ... };
    (c) class iostream: public istream, public ostream { ... };

Exercise 17.24:

Given the following class hierarchy, in which each class defines a default constructor,

给定下面的类层次,其中,每个类定义了一个默认构造函数。

    class A { ... };
    class B : public A { ... };
    class C : public B { ... };
    class X { ... };
    class Y { ... };
    class Z : public X, public Y { ... };
    class MI : public C, public Z { ... };

what is the order of constructor execution for the following definition?

对于下面的定义,构造函数的执行次序是什么?

    MI mi;


17.3.2. Conversions and Multiple Base Classes

17.3.2. 转换与多个基类

Under single inheritance, a pointer or a reference to a derived class can be converted automatically to a pointer or a reference to a base class. The same holds true with multiple inheritance. A pointer or reference to a derived class can be converted to a pointer or reference to any of its base classes. For example, a Panda pointer or reference could be converted to a pointer or a reference to ZooAnimal, Bear, or Endangered:

在单个基类情况下,派生类的指针或引用可以自动转换为基类的指针或引用,对于多重继承也是如此,派生类的指针或引用可以转换为其任意其类的指针或引用。例如,Panda 指针或引用可以转换为 ZooAnimalBearEndangered 的指针或引用。

    // operations that take references to base classes of type Panda
    void print(const Bear&);
    void highlight(const Endangered&);
    ostream& operator<<(ostream&, const ZooAnimal&);
    Panda ying_yang("ying_yang");    // create a Panda object
    print(ying_yang);      // passes Panda as reference to Bear
    highlight(ying_yang);  // passes Panda as reference to Endangered
    cout << ying_yang << endl;  // passes Panda as reference to ZooAnimal

Under multiple inheritance, there is a greater possibility of encountering an ambiguous conversion. The compiler makes no attempt to distinguish between base classes in terms of a derived-class conversion. Converting to each base class is equally good. For example, if there was an overloaded version of print

在多重继承情况下,遇到二义性转换的可能性更大。编译器不会试图根据派生类转换来区别基类间的转换,转换到每个基类都一样好。例如,如果有 print 函数的重载版本:

    void print(const Bear&);
    void print(const Endangered&);

an unqualified invocation of print with a Panda object

通过 Panda 对象对 print 函数的未限定调用

    Panda ying_yang("ying_yang");
    print(ying_yang);              // error: ambiguous

results in a compile-time error that the call is ambiguous.

导致一个编译时错误,指出该调用是二义性的。

Exercises Section 17.3.2

Exercise 17.25:

Given the following class hierarchy, in which each class defines a default constructor,

给定下面的类层次,其中每个类定义了一个默认构造函数,

    class X { ... };
    class A { ... };
    class B : public A { ... };
    class C : private B { ... };
    class D : public X, public C { ... };

which, if any, of the following conversions are not permitted?

如果有,下面转换中哪些是不允许的?

    D *pd = new D;
    (a) X *px = pd; (c) B *pb = pd;
    (b) A *pa = pd; (d) C *pc = pd;


Virtual Functions under Multiple Inheritance
多重继承下的虚函数

To see how the virtual function mechanism is affected by multiple inheritance, let's assume that our classes define the virtual members listed in Table 17.2.

为了看看多重继承怎样影响虚函数机制,假定我们的类定义表 17.2 中列出的虚成员。

Table 17.2. Virtual Function in the ZooAnimal/Endangered Classes
17.2. ZooAnimal/Endangered 类中的虚函数

Function

函数

Class Defining Own Version

定义自己版本的类

print

ZooAnimal::ZooAnimal

 

Bear::Bear

 

Endangered::Endangered

 

Panda::Panda

highlight

Endangered::Endangered

 

Panda::Panda

toes

Bear::Bear

 

Panda::Panda

cuddle

Panda::Panda

destructor

ZooAnimal::ZooAnimal

 

Endangered::Endangered


Lookup Based on Type of Pointer or Reference
基于指针类型或引用类型的查找

As with single inheritance, a pointer or reference to a base class can be used to access only members defined (or inherited) in the base. It cannot access members introduced in the derived class.

像单继承一样,用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员。

When a class inherits from multiple base classes, there is no implied relationship between those base classes. Using a pointer to one base does not allow access to members of another base.

当一个类继承于多个基类的时候,那些基类之间没有隐含的关系,不允许使用一个基类的指针访问其他基类的成员。

As an example, we could use a pointer or reference to a Bear, ZooAnimal, Endangered, or Panda to access a Panda object. The type of the pointer we use determines which operations are accessible. If we use a ZooAnimal pointer, only the operations defined in that class are usable. The Bear-specific, Panda-specific, and Endangered portions of the Panda interface are inaccessible. Similarly, a Bear pointer or reference knows only about the Bear and ZooAnimal members; an Endangered pointer or reference is limited to the Endangered members:

作为例子,我们可以使用 Bear、ZooAnimal、EndangeredPanda 的指针或引用来访问 Panda 对象。所用指针的类型决定了可以访问哪些操作。如果使用 ZooAnimal 指针,只能使用 ZooAnimal 类中定义的操作,不能访问 Panda 接口的 Bear 特定、Panda 特定和 Endangered 部分。类似地,Bear 指针或引用只知道 BearZooAnimal 成员,Endangered 指针或引用局限于 Endangered 成员:

    Bear *pb = new Panda("ying_yang");
    pb->print(cout); // ok: Panda::print(ostream&)
    pb->cuddle();    // error: not part of Bear interface
    pb->highlight(); // error: not part of Bear interface
    delete pb;       // ok: Panda::~Panda()

If the Panda object had been assigned to a ZooAnimal pointer, this set of calls would resolve exactly the same way.

如果将 Panda 对象赋给 ZooAnimal 指针,这个调用集合将完全相同的方式确定。

When a Panda is used via an Endangered pointer or reference, the Panda-specific and Bear portions of the Panda interface are inaccessible:

在通过 Endangered 指针或引用使用 Panda 对象的时候,不能访问 Panda 接口的 Panda 特定的部分和 Bear 部分:

    Endangered *pe = new Panda("ying_yang");
    pe->print(cout);  // ok: Panda::print(ostream&)
    pe->toes();       // error: not part of Endangered interface
    pe->cuddle();     // error: not part of Endangered interface
    pe->highlight();  // ok: Endangered::highlight()
    delete pe;        // ok: Panda::~Panda()

Determining Which Virtual Destructor to Use
确定使用哪个虚析构函数

Assuming all the root base classes properly define their destructors as virtual, then the handling of the virtual destructor is consistent regardless of the pointer type through which we delete the object:

假定所有根基类都将它们的析构函数适当定义为虚函数,那么,无论通过哪种指针类型删除对象,虚析构函数的处理都是一致的:

    // each pointer points to a Panda
    delete pz; // pz is a ZooAnimal*
    delete pb; // pb is a Bear*
    delete pp; // pp is a Panda*
    delete pe; // pe is a Endangered*

Assuming each of these pointers points to a Panda object, the exact same order of destructor invocations occurs in each case. The order of destructor invocations is the reverse of the constructor order: The Panda destructor is invoked through the virtual mechanism. Following execution of the Panda destructor, the Endangered, Bear, then ZooAnimal destructors are invoked in turn.

假定这些指针每个都向 Panda 对象,则每种情况下发生完全相同的析构函数调用次序。析构函数调用的次序是构造函数次序的逆序:通过虚机制调用 Panda 析构函数。随着 Panda 析构函数的执行,依次调用 EndangeredBearZooAnimal 的析构函数。

Exercises Section 17.3.2

Exercise 17.26:

On page 735 we presented a series of calls made through a Bear pointer that pointed to a Panda object. We noted that if the pointer had been a ZooAnimal pointer the calls would resolve the same way. Explain why.

本节给出了一系列通过指向 Panda 对象的 Bear 指针所进行的调用。我们指出,如果指针是 ZooAnimal 指针,将以同样方式确定函数调用。解释为什么。

Exercise 17.27:

Assume we have two base classes, Base1 and Base2, each of which defines a virtual member named print and a virtual destructor. From these base classes we derive the following classes each of which redefines the print function:

假定有两个基类 Base1Base2,其中每一个定义了一个名为 print 的虚成员和一个虚析构函数。从这些基类派生下面的类,其中每一个类都重定义了 print 函数:

    class D1 : public Base1 { /* ... */ };
    class D2 : public Base2 { /* ... */ };
    class MI : public D1, public D2 { /* ... */ };

Using the following pointers determine which function is used in each call:

使用下面的指针确定在每个调用中使用哪个函数:

    Base1 *pb1 = new MI; Base2 *pb2 = new MI;
    D1 *pd1 = new MI; D2 *pd2 = new MI;

    (a) pb1->print(); (b) pd1->print(); (c) pd2->print();
    (d) delete pb2;   (e) delete pd1;   (f) delete pd2;

Exercise 17.28:

Write the class definitions that correspond to Table 17.2 (p. 735).

编写与图 17.2 对应的类定义。


17.3.3. Copy Control for Multiply Derived Classes

17.3.3. 多重继承派生类的复制控制

The memberwise initialization, assignment and destruction (Chapter 13) of a multiply derived class behaves in the same way as under single inheritance. Each base class is implicitly constructed, assigned or destroyed, using that base class' own copy constructor, assignment operator or destructor.

多重继承的派生类的逐个成员初始化、赋值和析构(第十三章),表现得与单继承下的一样,使用基类自己的复制构造函数、赋值操作符或析构函数隐式构造、赋值或撤销每个基类。

Let's assume that Panda uses the default copy control members. Using the default copy constructor, the initialization of ling_ling

假定 Panda 类使用默认复制控制成员。ling_ling 的初始化

    Panda ying_yang("ying_yang");  // create a Panda object
    Panda ling_ling = ying_yang;   // uses copy constructor

invokes the Bear copy constructor, which in turn runs the ZooAnimal copy constructor prior to executing the Bear copy constructor. Once the Bear portion of ling_ling is constructed, the Endangered copy constructor is run to create that part of the object. Finally, the Panda copy constructor is run.

使用默认复制构造函数调用 Bear 复制构造函数,Bear 复制构造函数依次在执行 Bear 复制构造函数之前运行 ZooAnimal 复制构造函数。一旦构造了 ling_lingBear 部分,就运行 Endangered 复制构造函数来创建对象的那个部分。最后,运行 Panda 复制构造函数。

The synthesized assignment operator behaves similarly to the copy constructor. It assigns the Bear (and through Bear, the ZooAnimal) parts of the object first. Next, it assigns the Endangered part, and finally the Panda part.

合成的赋值操作符的行为类似于复制构造函数,它首先对对象的 Bear 部分进行赋值,并通过 Bear 对对象的 ZooAnimal 部分进行赋值,然后,对 Endangered 部分进行赋值,最后对 Panda 部分进行赋值。

The synthesized destructor destroys each member of the Panda object and calls the destructors for the base class parts, in reverse order from construction.

合成的析构函数撤销 Panda 对象的每个成员,并且按构造次序的逆序为基类部分调用析构函数。

As is the case for single inheritance (Section 15.4.3, p. 584), if a class with multiple bases defines its own destructor, that destructor is responsible only for cleaning up the derived class. If the derived class defines its own copy constructor or assignment operator, then the class is responsible for copying (assigning) all the base class subparts. The base parts are automatically copied or assigned only if the derived class uses the synthesized versions of these members.

像单继承(第 15.4.3 节)的情况一样,如果具有多个基类的类定义了自己的析构函数,该析构函数只负责清除派生类。如果派生类定义了自己的复制构造函数或赋值操作符,则类负责复制(赋值)所有的基类子部分。只有派生类使用复制构造函数或赋值操作符的合成版本,才自动复制或赋值基类部分。



17.3.4. Class Scope under Multiple Inheritance

17.3.4. 多重继承下的类作用域

Class scope (Section 15.5, p. 590) is more complicated in multiple inheritance because a derived scope may be enclosed by multiple base class scopes. As usual, name lookup for a name used in a member function starts in the function itself. If the name is not found locally, then lookup continues in the member's class and then searches each base class in turn. Under multiple inheritance, the search simultaneously examines all the base-class inheritance subtreesin our example, both the Endangered and the Bear/ZooAnimal subtrees are examined in parallel. If the name is found in more than one subtree, then the use of that name must explicitly specify which base class to use. Otherwise, the use of the name is ambiguous.

在多重继承下,类作用域(第 15.5 节)更加复杂,因为多个基类作用域可以包围派生类作用域。通常,成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在成员的类中查找,然后依次查找每个基类。在多重继承下,查找同时检察所有的基类继承子树——在我们的例子中,并行查找 Endangered 子树和 Bear/ZooAnimal 子树。如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的。

When a class has multiple base classes, name lookup happens simultaneously through all the immediate base classes. It is possible for a multiply derived class to inherit a member with the same name from two or more base classes. Unqualified uses of that name are ambiguous.

当一个类有多个基类的时候,通过所有直接基类同时进行名字查找。多重继承的派生类有可能从两个或多个基类继承同名成员,对该成员不加限定的使用是二义性的。



Multiple Base Classes Can Lead to Ambiguities
多个基类可能导致二义性

Assume both Bear and Endangered define a member named print. If Panda does not define that member, then a statement such as the following

假定 Bear 类和 Endangered 类都定义了名为 print 的成员,如果 Panda 类没有定义该成员,则

    ying_yang.print(cout);

results in a compile-time error.

这样的语句将导致编译时错误。

The derivation of Panda, which results in Panda having two members named print, is perfectly legal. The derivation results in only a potential ambiguity. That ambiguity is avoided if no Panda object ever calls print. The error would also be avoided if each call to print specifically indicated which version of print was wantedBear::print or Endangered::print. An error is issued only if there is an ambiguous attempt to use the member.

Panda 类的派生(它导致有两个名为 print 的成员)是完全合法的。派生只是导致潜在的二义性,如果没有 Panda 对象调用 print,就可以避免这个二义性。如果每个 print 调用明确指出想要哪个版本——Bear::print 还是 Endangered::print,也可以避免错误。只有在存在使用该成员的二义性尝试的时候,才会出错。

If a declaration is found only in one base-class subtree, then the identifier is resolved and the lookup algorithm concludes. For example, class Endangered might have an operation to return the given estimated population of its object. If so, the following call

如果只在一个基类子树中找到声明,则标识符得以确定而查找算法结束。例如,Endangered 类可能有一个操作返回给定其对象的估计数目,如果是这样,下面的调用

    ying_yang.population();

would compile without complaint. The name population would be found in the Endangered base class and does not appear in Bear or any of its base classes.

可以顺得编译,名字 population 将在基类 Endangered 中找到,并且在 Bear 类或其任意基类中都不会出现。

Name Lookup Happens First
首先发生名字查找

Although the ambiguity of the two inherited print members is reasonably obvious, it might be more surprising to learn that an error would be generated even if the two inherited functions had different parameter lists. Similarly, it would be an error even if the print function were private in one class and public or protected in the other. Finally, if print were defined in ZooAnimal and not Bear, the call would still be in error.

虽然两个继承的 print 成员的二义性相当明显,但是也许更令人惊讶的是,即使两个继承的函数有不同的形参表,也会产生错误。类似地,即使函数在一个类中是私有的而在另一个类中是公用或受保护的,也是错误的。最后,如果在 ZooAnimal 类中定义了 printBear 类中没有定义,调用仍是错误的。

As always, name lookup happens in two steps (Section 7.8.1, p. 268): First the compiler finds a matching declaration (or, in this case, two matching declarations, which causes the ambiguity). Only then does the compiler decide whether the declaration it found is legal.

名字查找总是以两个步骤发生(第 7.8.1 节):首先编译器找到一个匹配的声明(或者,在这个例子中,找到两个匹配的声明,这导致二义性),然后,编译器才确定所找到的声明是否合法。

Avoiding User-Level Ambiguities
避免用户二义性

We could resolve the print ambiguity by specifying which class to use:

可以通过指定使用哪个类解决二义性:

    ying_yang.Endangered::print(cout);

The best way to avoid potential ambiguities is to define a version of the function in the derived class that resolves the ambiguity. For example, we should give our Panda class a print function that chooses which version of print to use:

避免潜在二义性最好的方法是,在解决二义性的派生类中定义函数的一个版本。例如,应该给选择使用哪个 print 版本的 Panda 类一个 print 函数:

    std::ostream& Panda::print(std::ostream &os) const

    {
        Bear::print(os);        // print the Bear part
        Endangered::print(os);  // print the Endangered part
        return os;
    }

Code for Exercises to Section 17.3.4

本节习题的代码

   struct Base1 {
       void print(int) const;
    protected:
       int    ival;
       double dval;
       char   cval;
    private:
        int   *id;
    };
    struct Base2 {
        void print(double) const;
    protected:
        double fval;
    private:
        double dval;
    };
    struct Derived : public Base1 {
        void print(std::string) const;
    protected:
        std::string sval;
        double      dval;
    };
    struct MI : public Derived, public Base2 {
        void print(std::vector<double>);
    protected:
        int                  *ival;
        std::vector<double>  dvec;
    };


Exercises Section 17.3.4

Exercise 17.29:

Given the class hierarchy in the box on this page and the following MI::foo member function skeleton,

给定前面给出的类层次以及下面的 MI::foo 成员函数框架,

    int ival;
    double dval;

    void MI::foo(double dval) { int id; /* ... */ }

  1. identify the member names visible from within MI. Are there any names visible from more than one base class?

    识别从 MI 中可见的成员名字。有从多个基类中都可见的名字吗?

  2. identify the set of members visible from within MI::foo.

    识别从 MI::foo 中可见的成员的集合。

Exercise 17.30:

Given the hierarchy in the box on page 739, why is this call to print an error?

给定前面给出的类层次,为什么下面的这个 print 调用是错误的?

    MI mi;
    mi.print(42);

Revise MI to allow this call to print to compile and execute correctly.

修改 MI,使得这个 print 调用可以正确编译和执行。

Exercise 17.31:

Using the class hierarchy in the box on page 739, identify which of the following assignments, if any, are in error:

使用前面给出的类层次,如果下面赋值中有错误的,识别哪些是错误的:

    void MI::bar() {
        int sval;
        // exercise questions occur here ...
    }
    (a) dval = 3.14159;   (d) fval = 0;
    (b) cval = 'a';       (e) sval = *ival; (c) id = 1;

Exercise 17.32:

Using the class hierarchy defined in the box on page 739 and the following skeleton of the MI::foobar member function

使用前面给出的类层次以及下面的 MI::foobar 成员函数框架

    void MI::foobar(double cval)
    {
        int dval;
        // exercise questions occur here ...
    }

  1. assign to the local instance of dval the sum of the dval member of Base1 and the dval member of Derived.

    Base1dval 成员和 Deriveddval 成员的和赋给 dval 的局部实例。

  2. assign the value of the last element in MI::dvec to Base2::fval.

    MI::dvec 中最后一个元素赋给 Base2::fval

  3. assign cval from Base1 to the first character in sval from Derived.

    Base1cval 赋给 Derivedsval 的第一个字符。


17.3.5. Virtual Inheritance

17.3.5. 虚继承

Under multiple inheritance, a base class can occur multiple times in the derivation hierarchy. In fact, our programs have already used a class that inherits from the same base class more than once through its inheritance hierarchy.

在多重继承下,一个基类可以在派生层次中出现多次。实际上,我们的程序已经使用过通过继承层次多次继承同一基类的类。

Each of the IO library classes inherits from a common abstract base class. That abstract class manages the condition state of the stream and holds the buffer that the stream reads or writes. The istream and ostream classes inherit directly from this common base class. The library defines another class, named iostream, that inherits from both istream and ostream. The iostream class can both read and write a stream. A simplified version of the IO inheritance hierarchy is illustrated in Figure 17.3 on the facing page.

每个 IO 库类都继承了一个共同的抽象基类,那个抽象基类管理流的条件状态并保存流所读写的缓冲区。istreamostream 类直接继承这个公共基类,库定义了另一个名为 iostream 的类,它同时继承 istreamostreamiostream 类既可以对流进行读又可以对流进行写。IO 继承层次的简化版本如图 17.3 所示。

Figure 17.3. Virtual Inheritance iostream Hierarchy (Simplified)
17.3. 虚继承 iostream 层次(简化的)


As we know, a multiply inherited class inherits state and action from each of its parents. If the IO types used normal inheritance, then each iostream object would contain two ios subobjects: one instance contained within its istream subobject and the other within its ostream subobject. From a design perspective, this implementation is just wrong: The iostream class wants to read to and write from a single buffer; it wants the condition state to be shared across input and output operations. If there are two separate ios objects, this sharing is not possible.

像我们知道的那个,多重继承的类从它的每个父类继承状态和动作,如果 IO 类型使用常规继承,则每个 iostream 对象可能包含两个 ios 子对象:一个包含在它的 istream 子对象中,另一个包含在它的 ostream 子对象中,从设计角度讲,这个实现正是错误的:iostream 类想要对单个缓冲区进行读和写,它希望跨越输入和输出操作符共享条件状态。如果有两个单独的 ios 对象,这种共享是不可能的。

In C++ we solve this kind of problem by using virtual inheritance. Virtual inheritance is a mechanism whereby a class specifies that it is willing to share the state of its virtual base class. Under virtual inheritance, only one, shared base-class subobject is inherited for a given virtual base regardless of how many times the class occurs as a virtual base within the derivation hierarchy. The shared base-class subobject is called a virtual base class.

在 C++ 中,通过使用虚继承解决这类问题。虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类

The istream and ostream classes inherit virtually from their base class. By making their base class virtual, istream and ostream specify that if some other class, such as iostream, inherits from both of them, then only one copy of their common base class will be present in the derived class. We make a base class virtual by including the keyword virtual in the derivation list:

istreamostream 类对它们的基类进行虚继承。通过使基类成为虚基类,istreamostream 指定,如果其他类(如 iostream 同时继承它们两个,则派生类中只出现它们的公共基类的一个副本。通过在派生列表中包含关键字 virtual 设置虚基类:

    class istream : public virtual ios { ... };
    class ostream : virtual public ios { ... };

    // iostream inherits only one copy of its ios base class
    class iostream: public istream, public ostream { ... };

A Different Panda Class
一个不同的 Panda 类

For the purposes of illustrating virtual inheritance, we'll continue to use the Panda class as a pedagogical example. Within zoological circles, for more than 100 years there has been an occasionally fierce debate as to whether the Panda belongs to the Raccoon or the Bear family. Because software design is primarily a service industry, our most practical solution is to derive Panda from both:

为了举例说明虚继承,我们将继续使用 Panda 类作为教学例子。在动物学圈子中,对于 Panda 是属于 Raccoon 科还是 Bear 科已经争论了 100 年以上。因为软件设计主要是一种服务行业,所以最现实的解决方案是从二者派生 Panda

    class Panda : public Bear,
                  public Raccoon, public Endangered {
    };

Our virtual inheritance Panda hierarchy is pictured in Figure 17.4. If we examine that hierarchy, we notice a nonintuitive aspect of virtual inheritance: The virtual derivation (in our case, of Bear and Raccoon) has to be made prior to any actual need for it to be present. Virtual inheritance becomes necessary only with the declaration of Panda, but if Bear and Raccoon are not already virtually derived, the designer of the Panda class is out of luck.

虚继承 Panda 层次如图 17.4 所示。如果检查该层次,我们注意到虚继承一个不直观的特征:必须在提出虚派生的任意实际需要之前进行虚派生(在例中,Bear 类和 Raccoon 类的虚派生)。只有在使用 Panda 的声明时,虚继承才是必要的,但如果 Bear 类和 Raccoon 类不是虚派生的,Panda 类的设计者就没有好运气了。

Figure 17.4. Virtual Inheritance Panda Hierarchy
17.4. 虚继承 Panda 层次


In practice, the requirement that an intermediate base class specify its inheritance as virtual rarely causes any problems. Ordinarily, a class hierarachy that uses virtual inheritance is designed at one time by either one individual or a project design group. It is exceedingly rare for a class to be developed independently that needs a virtual base in one of its base classes and in which the developer of the new base class cannot change the existing hierarchy.

实际上,中间基类指定其继承为虚继承的要求很少引起任何问题。通常,使用虚继承的类层次是一次性由一个人或一个项目设计组设计的,独立开发的类很少需要其基类中的一个是虚基类,而且新基类的开发者不能改变已经存在的层次。

17.3.6. Virtual Base Class Declaration

17.3.6. 虚基类的声明

A base class is specified as being derived through virtual inheritance by modifying its declaration with the keyword virtual. For example, the following declarations make ZooAnimal a virtual base class of both Bear and Raccoon:

通过用关键字 virtual 修改声明,将基类指定为通过虚继承派生。例如,下面的声明使 ZooAnimal 类成为 Bear 类和 Raccoon 类的虚基类:

    // the order of the keywords public and virtual is not significant
    class Raccoon : public virtual ZooAnimal { /* ... */ };
    class Bear : virtual public ZooAnimal { /* ... */ };

Specifying virtual derivation has an impact only in classes derived from the class that specifies a virtual base. Rather than affecting objects of the derived class' own type, it is a statement about the derived class' relationship to its own, future derived class.

指定虚派生只影响从指定了虚基类的类派生的类。除了影响派生类自己的对象之外,它也是关于派生类与自己的未来派生类的关系的一个陈述。



The virtual specifier states a willingness to share a single instance of the named base class within a subsequently derived class.

virtual 说明符陈述了在后代派生类中共享指定基类的单个实例的愿望。

Any class that can be specified as a base class also could be specified as a virtual base class. A virtual base may contain any class element normally supported by a nonvirtual base class.

任何可被指定为基类的类也可以被指定为虚基类,虚基类可以包含通常由非虚基类支持的任意类元素。

Normal Conversions to Base Are Supported
支持到基类的常规转换

An object of the derived class can be manipulated as usual through a pointer or a reference to a base-class type even though the base class is virtual. For example, all of the following Panda base class conversions execute correctly even though Panda inherits its ZooAnimal part as a virtual base:

即使基类是虚基类,也照常可以通过基类类型的指针或引用操纵派生类的对象。例如,即使 Panda 类将它的 ZooAnimal 部分作为虚基类继承,下面所有 Panda 的基类转换也能正确执行:

    void dance(const Bear*);
    void rummage(const Raccoon*);
    ostream& operator<<(ostream&, const ZooAnimal&);
    Panda ying_yang;
    dance(&ying_yang);   // ok: converts address to pointer to Bear
    rummage(&ying_yang); // ok: converts address to pointer to Raccoon
    cout << ying_yang;   // ok: passes ying_yang as a ZooAnimal

Visibility of Virtual Base-Class Members
虚基类成员的可见性

Multiple-inheritance hierarchies using virtual bases pose fewer ambiguity problems than do those without virtual inheritance.

使用虚基类的多重继承层次比没有虚继承的引起更少的二义性问题。

Members in the shared virtual base can be accessed unambiguously and directly. Similarly, if a member from the virtual base is redefined along only one derivation path, then that redefined member can be accessed directly. Under a nonvirtual derivation, both kinds of access would be ambiguous.

可以无二义性地直接访问共享虚基类中的成员。同样,如果只沿一个派生路径重定义来自虚基类的成员,则可以直接访问该重定义成员。在非虚派生情况下,两种访问都可能是二义性的。



Assume a member named X is inherited through more than one derivation path. There are three possibilities:

假定通过多个派生路径继承名为 X 的成员,有下面三种可能性:

  1. If in each path X represents the same virtual base class member, then there is no ambiguity because a single instance of the member is shared.

    如果在每个路径中 X 表示同一虚基类成员,则没有二义性,因为共享该成员的单个实例。

  2. If in one path X is a member of the virtual base class member and in another path X is a member of a subsequently derived class, there is also no ambiguitythe specialized derived class instance is given precedence over the shared virtual base class instance.

    如果在某个路径中 X 是虚基类的成员,而在另一路径中 X 是后代派生类的成员,也没有二义性——特定派生类实例的优先级高于共享虚基类实例。

  3. If along each inheritance path X represents a different member of a subsequently derived class, then the direct access of the member is ambiguous.

    如果沿每个继承路径 X 表示后代派生类的不同成员,则该成员的直接访问是二义性的。

As in a nonvirtual multiple inheritance hierarchy, ambiguities of this sort are best resolved by the class providing an overriding instance in the derived class.

像非虚多重继承层次一样,这种二义性最好用在派生类中提供覆盖实例的类来解决。

Exercises Section 17.3.6

Exercise 17.33:

Given the following class hierarchy, which inherited members can be accessed without qualification from within the VMI class? Which require qualification? Explain your reasoning.

给定下面的类层次,从 VMI 类内部可以限定地访问哪些继承成员?哪些继承成员需要限定?解释你的推理。

    class Base {
    public:
        bar(int);
    protected:
        int ival;
    };
    class Derived1 : virtual public Base {
    public:
        bar(char);
        foo(char);
    protected:
        char cval;
    };
    class Derived2 : virtual public Base {
    public:
        foo(int);
    protected:
        int ival;
        char cval;
    };
    class VMI : public Derived1, public Derived2 { };


17.3.7. Special Initialization Semantics

17.3.7. 特殊的初始化语义

Ordinarily each class initializes only its own direct base class(es). This initialization strategy fails when applied to a virtual base class. If the normal rules were used, then the virtual base might be initialized multiple times. The class would be initialized along each inheritance path that contains the virtual base. In our ZooAnimal example, using normal initialization rules would result in both Bear and Raccoon attempting to initialize the ZooAnimal part of a Panda object.

通常,每个类只初始化自己的直接基类。在应用于虚基类的进修,这个初始化策略会失败。如果使用常规规则,就可能会多次初始化虚基类。类将沿着包含该虚基类的每个继承路径初始化。在 ZooAnimal 示例中,使用常规规则将导致 Bear 类和 Raccoon 类都试图初始化 Panda 对象的 ZooAnimal 类部分。

To solve this duplicate-initialization problem, classes that inherit from a class that has a virtual base have special handling for initialization. In a virtual derivation, the virtual base is initialized by the most derived constructor. In our example, when we create a Panda object, the Panda constructor alone controls how the ZooAnimal base class is initialized.

为了解决这个重复初始化问题,从具有虚基类的类继承的类对初始化进行特殊处理。在虚派生中,由最低层派生类的构造函数初始化虚基类。在我们的例子中,当创建 Panda 对象的时候,只有 Panda 构造函数控制怎样初始化 ZooAnimal 基类。

Although the virtual base is initialized by the most derived class, any classes that inherit immediately or indirectly from the virtual base usually also have to provide their own initializers for that base. As long as we can create independent objects of a type derived from a virtual base, that class must initialize its virtual base. These initializers are used only when we create objects of the intermediate type.

虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式。只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化式只有创建中间类型的对象时使用。

In our hierarchy, we could have objects of type Bear, Raccoon, or Panda. When a Panda is created, it is the most derived type and controls initialization of the shared ZooAnimal base. When a Bear (or a Raccoon) is created, there is no further derived type involved. In this case, the Bear (or Raccoon) constructors directly initialize their ZooAnimal base as usual:

在我们的层次中,可以有 BearRaccoonPanda 类型的对象。创建 Panda 对象的时候,它是最低层派生类型并控制共享的 ZooAnimal 基类的初始化:创建 Bear 对象(或 Raccoon 对象)的进修,不涉及更低层的派生类型。在这种情况下,Bear(或 Raccoon)构造函数像平常一样直接初始化它们的 ZooAnimal 基类:

    Bear::Bear(std::string name, bool onExhibit):
             ZooAnimal(name, onExhibit, "Bear") { }
    Raccoon::Raccoon(std::string name, bool onExhibit)
           : ZooAnimal(name, onExhibit, "Raccoon") { }

The Panda constructor also initializes the ZooAnimal base, even though it is not an immediate base class:

虽然 ZooAnimal 不是 Panda 的直接基类,但是 Panda 构造函数也初始化 ZooAnimal 基类:

    Panda::Panda(std::string name, bool onExhibit)
          : ZooAnimal(name, onExhibit, "Panda"),
            Bear(name, onExhibit),
            Raccoon(name, onExhibit),
            Endangered(Endangered::critical),
            sleeping_flag(false) { }

When a Panda is created, it is this constructor that initializes the ZooAnimal part of the Panda object.

创建 Panda 对象的时候,这个构造函数初始化 Panda 对象的 ZooAnimal 部分。

How a Virtually Inherited Object Is Constructed
怎样构造虚继承的对象

Let's look at how objects under virtual inheritance are constructed.

让我们看看虚继承情况下怎样构造对象。

Bear winnie("pooh");    // Bear constructor initializes ZooAnimal
Raccoon meeko("meeko"); // Raccoon constructor initializes ZooAnimal
Panda yolo("yolo");     // Panda constructor initializes ZooAnimal

When a Panda object is created,

当创建 Panda 对象的时候,

  1. The ZooAnimal part is constructed first, using the initializers specified in the Panda constructor initializer list.

    首先使用构造函数初始化列表中指定的初始化式构造 ZooAnimal 部分。

  2. Next, the Bear part is constructed. The initializers for ZooAnimal Bear's constructor initializer list are ignored.

    接下来,构造 Bear 部分。忽略 Bear 的用于 ZooAnimal 构造函数初始化列表的初始化式。

  3. Then the Raccoon part is constructed, again ignoring the ZooAnimal.

    然后,构造 Raccoon 部分,再次忽略 ZooAnimal 初始化式。

  4. Finally, the Panda part is constructed.

    最后,构造 Panda 部分。

If the Panda constructor does not explicitly initialize the ZooAnimal base class, then the ZooAnimal default constructor is used. If ZooAnimal doesn't have a default constructor, then the code is in error. The compiler will issue an error message when the definition of Panda's constructor is compiled.

如果 Panda 构造函数不显式初始化 ZooAnimal 基类,就使用 ZooAnimal 默认构造函数;如果 ZooAnimal 没有默认构造函数,则代码出错。当编译 Panda 构造函数的定义时,编译器将给出一个错误信息。

Constructor and Destructor Order
构造函数与析构函数次序

Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy.

无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。



For example, in the following whimsical TeddyBear derivation, there are two virtual base classes: the ToyAnimal base class and the indirect ZooAnimal base class from which Bear is derived:

例如,下面毫无规律的 TeddyBear(图 17.5)派生中,有两个虚基类:ToyAnimal 基类和派生 Bear 的间接基类 ZooAnimal

   class Character { /* ... */ };
   class BookCharacter : public Character { /* ... */ };
   class ToyAnimal { /* ... */ };
   class TeddyBear : public BookCharacter,
                     public Bear, public virtual ToyAnimal
                     { /* ... */ };

Figure 17.5. Virtual Inheritance TeddyBear Hierarchy
图 17.5. 虚继承 TeddyBear 层次


The immediate base classes are examined in declaration order to determine whether there are any virtual base classes. In our example, the inheritance subtree of BookCharacter is examined first, then that of Bear, and finally that of ToyAnimal. Each subtree is examined starting at the root class down to the most derived class.

按声明次序检查直接基类,确定是否存在虚基类。例中,首先检查 BookCharacter 的继承子树,然后检查 Bear 的继承子树,最后检查 ToyAnimal 的继承子树。按从根类开始向下到最低层派生类的次序检查每个子树。

The order in which the virtual base classes are constructed for TeddyBear is ZooAnimal followed by ToyAnimal. Once the virtual base classes are constructed, the nonvirtual base-class constructors are invoked in declaration order: BookCharacter, which causes the Character constructor to be invoked, and then Bear. Thus, to create a TeddyBear, the constructors are invoked in the following order:

TeddyBear 的虚基类的构造次序是先 ZooAnimalToyAnimal。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是 BookCharacter,它导致调用 Character 构造函数,然后是 Bear。因此,为了创建 TeddyBear 对象,按下面次序调用构造函数:

    ZooAnimal();           // Bear's virtual base class
    ToyAnimal();           // immediate virtual base class
    Character();           // BookCharacter's nonvirtual base class
    BookCharacter();       // immediate nonvirtual base class
    Bear();                // immediate nonvirtual base class
    TeddyBear();           // most derived class

where the initializers used for ZooAnimal and ToyAnimal are specified by the most derived class TeddyBear.

在这里,由最低层派生类 TeddyBear 指定用于 ZooAnimalToyAnimal 的初始化式。

The same construction order is used in the synthesized copy constructor; the base classes also are assigned in this order in the synthesized assignment operator. The order of base-class destructor calls is guaranteed to be the reverse order of constructor invocation.

在合成复制构造函数中使用同样的构造次序,在合成赋值操作符中也是按这个次序给基类赋值。保证调用基类析构函数的次序与构造函数的调用次序相反。

Exercises Section 17.3.7

Exercise 17.34:

There is one case in which a derived class need not supply initializers for its virtual base class(es). What is this case?

有一种情况下派生类不必为虚基类提供初始化式,这种情况是什么?

Exercise 17.35:

Given the following class hierarchy,

给定下面类层次,

    class Class { ... };
    class Base : public Class { ... };
    class Derived1 : virtual public Base { ... };
    class Derived2 : virtual public Base { ... };
    class MI : public Derived1,
               public Derived2 { ... };
    class Final : public MI, public Class { ... };

  1. What is the order of constructor and destructor for the definition of a Final object?

    一个 Final 对象中有几个 Base 子对象?

  2. How many Base subobjects are in a Final object? How many Class subobjects?

    有几个 class 子对象?

  3. Which of the following assignments is a compile-time error?

    下面哪个赋值在编译时有错?

    Base     *pb;      Class     *pc;
    MI       *pmi;     Derived2  *pd2;

    (a) pb = new Class;           (c) pmi = pb;
    (b) pc = new Final;           (d) pd2 = pmi;

Exercise 17.36:

Given the previous hierarchy, and assuming that Base defines the following three constructors, define the classes that inherit from Base, giving each class the same three constructors. Each constructor should use its argument to initialize its Base part.

给定前面的层次,并假定 Base 定义了下面三个构造函数,定义从 Base 派生的类,给每个类同样的三个构造函数,每个构造函数使用实参初始化其 Base 部分。

    struct Base {
        Base();
        Base(std::string);
        Base(const Base&);
    protected:
        std::string name;
    };


Team LiB
Previous Section Next Section