Repairing
an interface
One
of the best arguments 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 is incomplete: A
function may be nonvirtual 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. If you had the source code, you could go back and put it in. But you
don’t, and you have a lot of existing code that depends on the original
interface. Here, multiple inheritance is the perfect solution.
For
example, here’s the header file for a library you acquire:
//: C22: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 pointer and treat it polymorphically. Here’s the
implementation file for the library:
//: C22:Vendor.cpp {O}
// Implementation of VENDOR.H
// This is compiled and unavailable to you
#include <fstream>
#include "Vendor.h"
using namespace std;
extern ofstream out; // For trace info
void Vendor::v() const {
out << "Vendor::v()\n";
}
void Vendor::f() const {
out << "Vendor::f()\n";
}
Vendor::~Vendor() {
out << "~Vendor()\n";
}
void Vendor1::v() const {
out << "Vendor1::v()\n";
}
void Vendor1::f() const {
out << "Vendor1::f()\n";
}
Vendor1::~Vendor1() {
out << "~Vendor1()\n";
}
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 the equivalent for your system).
The
problem occurs in the use of this library. First, the destructor isn’t
virtual. This is actually a design error on the part of the library creator. In
addition,
f( )
was not made virtual; assume the library creator decided it wouldn’t need
to be. And you 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:
//: C22:Paste.cpp
//{L} Vendor
// Fixing a mess with MI
#include <fstream>
#include "Vendor.h"
using namespace std;
ofstream out("paste.out");
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() { out << "~MyBase()\n"; }
};
class Paste1 : public MyBase, public Vendor1 {
public:
void v() const {
out << "Paste1::v()\n";
Vendor1::v();
}
void f() const {
out << "Paste1::f()\n";
Vendor1::f();
}
void g() const {
out << "Paste1::g()\n";
}
~Paste1() { out << "~Paste1()\n"; }
};
int main() {
Paste1& p1p = *new Paste1;
MyBase& mp = p1p; // Upcast
out << "calling f()\n";
mp.f(); // Right behavior
out << "calling g()\n";
mp.g(); // New behavior
out << "calling A(p1p)\n";
A(p1p); // Same old behavior
out << "calling B(p1p)\n";
B(p1p); // Same old behavior
out << "delete mp\n";
// 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( )
has been added to the interface. Now each of the derived classes in the
original library must be recreated, 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.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru