Выбрать главу

//: C09:BreakTie.cpp

class Top {};

class Left : virtual public Top {

public:

   void f(){}

};

class Right : virtual public Top {

public:

   void f(){}

};

class Bottom : public Left, public Right {

public:

  using Left::f;

};

int main() {

   Bottom b;

   b.f();     // calls Left::f()

} ///:~

The name Left::f is now found in the scope of Bottom, so the name Right::f is not even considered. Of course, if you want to introduce extra functionality beyond what Left::f( ) provides, you would implement a Bottom::f( ) function that calls Left::f( ), in addition to other things.

Functions with the same name occurring in different branches of a hierarchy often conflict. The following hierarchy has no such problem:

//: C09:Dominance.cpp

class Top {

public:

  virtual void f() {}

};

class Left : virtual public Top {

public:

   void f(){}

};

class Right : virtual public Top {

};

class Bottom : public Left, public Right {};

int main() {

   Bottom b;

   b.f(); // calls Left::f()

} ///:~

In this case, there is no explicit Right::f( ), so Left::f( ), being the most derived, is chosen. Why? Well, pretend that Right did not exist, giving the single-inheritance hierarchy Top <= Left <= Bottom. You would certainly expect Left::f( ) to be the function called by the expression b.f( ), because of normal scope rules (a derived class is considered a nested scope of a base class). In general, a name A::f is said to dominate the name B::f if A derives from B, directly or indirectly, or in other words, if A is "more derived" in the hierarchy than B.[113] To summarize: in choosing between two functions with the same name, one of which dominates the other, the compiler chooses the one that dominates. If there is no dominant function, there is an ambiguity.

 The following program further illustrates the dominance principle.

//: C09:Dominance2.cpp

#include <iostream>

using namespace std;

class A {

public:

   virtual void f() {cout << "A::f\n";}

};

class B : virtual public A {

public:

   void f() {cout << "B::f\n";}

};

class C : public B {};

class D : public C, virtual public A {};

int main()

{

   B* p = new D;

   p->f();  // calls B::f()

} ///:~

The class diagram for this hierarchy is as follows.

The class A is a (direct, in this case) base class for B, and so the name B::f dominates A::f.

Avoiding MI

When the question of whether to use multiple inheritance comes up, ask at least two questions:

1.Do you need to show the public interfaces of both these classes through your new type, or could one class be contained within the other, with only some of its interface exposed in the new class?

2.Do you need to upcast to both of the base classes? (This applies when you have more than two base classes, of course.)

If you can answer "yes" to either question, you can avoid using MI and should probably do so.

One situation to watch for is when one class needs to be upcast only as a function argument. In that case, the class can be embedded and an automatic type conversion operator provided in your new class to produce a reference to the embedded object. Any time you use an object of your new class as an argument to a function that expects the embedded object, the type conversion operator is used.[114] However, type conversion can’t be used for normal member selection; that requires inheritance.

Extending an interface

One of the best uses for multiple inheritance involves code that’s out of your control. Suppose you’ve acquired a library that consists of a header file and compiled member functions, but no source code for member functions. This library is a class hierarchy with virtual functions, and it contains some global functions that take pointers to the base class of the library; that is, it uses the library objects polymorphically. Now suppose you build an application around this library and write your own code that uses the base class polymorphically.

Later in the development of the project or sometime during its maintenance, you discover that the base-class interface provided by the vendor doesn’t provide what you need: a function may be non-virtual and you need it to be virtual, or a virtual function is completely missing in the interface, but essential to the solution of your problem. Multiple inheritance is the perfect solution.

For example, here’s the header file for a library you acquire:

//: C09:Vendor.h

// Vendor-supplied class header

// You only get this & the compiled Vendor.obj

#ifndef VENDOR_H

#define VENDOR_H

class Vendor {

public:

  virtual void v() const;

  void f() const;

  ~Vendor();

};

class Vendor1 : public Vendor {

public:

  void v() const;

  void f() const;

  ~Vendor1();

};

void A(const Vendor&);

void B(const Vendor&);

// Etc.

#endif // VENDOR_H ///:~

Assume the library is much bigger, with more derived classes and a larger interface. Notice that it also includes the functions A( ) and B( ), which take a base reference and treat it polymorphically. Here’s the implementation file for the library:

//: C09:Vendor.cpp {O}

// Implementation of VENDOR.H

// This is compiled and unavailable to you

#include <iostream>

#include "Vendor.h"

using namespace std;

void Vendor::v() const {

  cout << "Vendor::v()\n";

}

void Vendor::f() const {

  cout << "Vendor::f()\n";

}

Vendor::~Vendor() {

  cout << "~Vendor()\n";

}

void Vendor1::v() const {

  cout << "Vendor1::v()\n";

}

void Vendor1::f() const {

вернуться

113

Note that the virtual inheritance is crucial to this example. If Top were not a virtual base class, there would be multiple Top subobjects, and the ambiguity would remain. Dominance with multiple inheritance only comes into play with virtual base classes.

вернуться

114

Jerry Schwarz, the author of IOStreams, has remarked to both of us on separate occasions that if he had it to do over again, he would probably remove MI from the design of IOStreams and use multiple stream buffers and conversion operators instead.