The derivation list in a derived class can contain more than one base class:
class Bear : public ZooAnimal {
class Panda : public Bear, public Endangered { /* ... */ };
Each base class has an optional access specifier (§ 15.5, p. 612). As usual, if the access specifier is omitted, the specifier defaults to private
if the class
keyword is used and to public
if struct
is used (§ 15.5, p. 616).
As with single inheritance, the derivation list may include only classes that have been defined and that were not defined as final
(§ 15.2.2, p. 600). 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.
Under multiple inheritance, an object of a derived class contains a subobject for each of its base classes (§ 15.2.2, p. 597). For example, as illustrated in Figure 18.2, a Panda
object has a Bear
part (which itself contains a ZooAnimal
part), an Endangered
class part, and the nonstatic
data members, if any, declared within the Panda
class.
Constructing an object of derived type constructs and initializes all its base subobjects. As is the case for inheriting from a single base class (§ 15.2.2, p. 598), a derived type’s constructor initializer may initialize only its direct base classes:
// explicitly initialize both base classes
Panda::Panda(std::string name, bool onExhibit)
: Bear(name, onExhibit, "Panda"),
Endangered(Endangered::critical) { }
// implicitly uses the Bear default constructor to initialize the Bear subobject
Panda::Panda()
: Endangered(Endangered::critical) { }
The constructor initializer list may pass arguments to each of the direct base classes. The order in which base classes are constructed depends on the order in which they appear in the class derivation list. The order in which they appear in the constructor initializer list is irrelevant. A Panda
object is initialized as follows:
• ZooAnimal
, the ultimate base class up the hierarchy from Panda
’s first direct base class, Bear
, is initialized first.
• Bear
, the first direct base class, is initialized next.
• Endangered
, the second direct base, is initialized next.
• Panda
, the most derived part, is initialized last.
Under the new standard, a derived class can inherit its constructors from one or more of its base classes (§ 15.7.4, p. 628). It is an error to inherit the same constructor (i.e., one with the same parameter list) from more than one base class:
struct Base1 {
Base1() = default;
Base1(const std::string&);
Base1(std::shared_ptr<int>);
};
struct Base2 {
Base2() = default;
Base2(const std::string&);
Base2(int);
};
// error: D1 attempts to inherit D1::D1 (const string&) from both base classes
struct D1: public Base1, public Base2 {
using Base1::Base1; // inherit constructors from Base1
using Base2::Base2; // inherit constructors from Base2
};
A class that inherits the same constructor from more than one base class must define its own version of that constructor:
struct D2: public Base1, public Base2 {
using Base1::Base1; // inherit constructors from Base1
using Base2::Base2; // inherit constructors from Base2
// D2 must define its own constructor that takes a string
D2(const string &s): Base1(s), Base2(s) { }
D2() = default; // needed once D2 defines its own constructor
};
As usual, the destructor in a derived class is responsible for cleaning up resources allocated by that class only—the members and all the base class(es) of the derived class are automatically destroyed. The synthesized destructor has an empty function body.
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
.
As is the case for single inheritance, classes with multiple bases that define their own copy/move constructors and assignment operators must copy, move, or assign the whole object (§ 15.7.2, p. 623). The base parts of a multiply derived class are automatically copied, moved, or assigned only if the derived class uses the synthesized versions of these members. In the synthesized copy-control members, each base class is implicitly constructed, assigned, or destroyed, using the corresponding member from that base class.
For example, assuming that Panda
uses the synthesized members, then the initialization of ling_ling
:
Panda ying_yang("ying_yang");
Panda ling_ling = ying_yang; // uses the copy constructor
will invoke the Bear
copy constructor, which in turn runs the ZooAnimal
copy constructor before 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. Similarly, for the synthesized move constructor.
The synthesized copy-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. Move assignment behaves similarly.