Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
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 can
be the 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; // Might want this to be virtual...
~Vendor(); // Oops! Not virtual!
};
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}
// Assume this is compiled and unavailable to you.
#include "Vendor.h"
#include <iostream>
using namespace std;
void Vendor::v() const { cout <<
"Vendor::v()" << endl; }
void Vendor::f() const { cout <<
"Vendor::f()" << endl; }
Vendor::~Vendor() { cout << "~Vendor()"
<< endl; }
void Vendor1::v() const { cout <<
"Vendor1::v()" << endl; }
void Vendor1::f() const { cout <<
"Vendor1::f()" << endl; }
Vendor1::~Vendor1() { cout <<
"~Vendor1()" << endl; }
void A(const Vendor& v) {
// ...
v.v();
v.f();
// ...
}
void B(const Vendor& v) {
// ...
v.v();
v.f();
// ...
} ///:~
In your project, this source code is unavailable to you.
Instead, you get a compiled file as Vendor.obj or Vendor.lib (or
with the equivalent file suffixes for your system).
The problem occurs in the use of this library. First, the
destructor isn t virtual. In
addition, f( ) was not made virtual; we assume the library creator
decided it wouldn t need to be. You also discover that the interface to the
base class is missing a function essential to the solution of your problem.
Also suppose you ve already written a fair amount of code using the existing
interface (not to mention the functions A( ) and B( ),
which are out of your control), and you don t want to change it.
To repair the problem, create your own class interface and
multiply inherit a new set of derived classes from your interface and from the
existing classes:
//: C09:Paste.cpp
//{L} Vendor
// Fixing a mess with MI.
#include <iostream>
#include "Vendor.h"
using namespace std;
class MyBase { // Repair Vendor interface
public:
virtual void v() const = 0;
virtual void f() const = 0;
// New interface function:
virtual void g() const = 0;
virtual ~MyBase() { cout <<
"~MyBase()" << endl; }
};
class Paste1 : public MyBase, public Vendor1 {
public:
void v() const {
cout << "Paste1::v()" <<
endl;
Vendor1::v();
}
void f() const {
cout << "Paste1::f()" <<
endl;
Vendor1::f();
}
void g() const { cout << "Paste1::g()
<< endl; }
~Paste1() { cout << "~Paste1() <<
endl; }
};
int main() {
Paste1& p1p = *new Paste1;
MyBase& mp = p1p; // Upcast
cout << "calling f() << endl;
mp.f(); // Right behavior
cout << "calling g() << endl;
mp.g(); // New behavior
cout << "calling A(p1p) << endl;
A(p1p); // Same old behavior
cout << "calling B(p1p) << endl;
B(p1p); // Same old behavior
cout << "delete mp << endl;
// Deleting a reference to a heap object:
delete ∓ // Right behavior
} ///:~
In MyBase (which does not use MI), both f( )
and the destructor are now virtual, and a new virtual function g( )
is added to the interface. Now each of the derived classes in the original
library must be re-created, mixing in the new interface with MI. The functions Paste1::v( )
and Paste1::f( ) need to call only the original base-class versions
of their functions. But now, if you upcast to MyBase as in main( ):
MyBase* mp = p1p; // Upcast
any function calls made through mp will be
polymorphic, including delete. Also, the new interface function g( )
can be called through mp. Here s the output of the program:
calling f()
Paste1::f()
Vendor1::f()
calling g()
Paste1::g()
calling A(p1p)
Paste1::v()
Vendor1::v()
Vendor::f()
calling B(p1p)
Paste1::v()
Vendor1::v()
Vendor::f()
delete mp
~Paste1()
~Vendor1()
~Vendor()
~MyBase()
The original library functions A( ) and B( )
still work the same (assuming the new v( ) calls its base-class
version). The destructor is now virtual and exhibits the correct
behavior.
Although this is a messy example, it does occur in practice,
and it s a good demonstration of where multiple inheritance is clearly
necessary: You must be able to upcast to both base classes.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |