1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
Except for the class tt(Randbuf) classes thus far have always been derived
from a single base class. In addition to i(single inheritance)
hi(inheritance: multiple)
bf(C++) also supports emi(multiple inheritance). In multiple inheritance a
class is derived from several base classes and hence inherits functionality
from multiple parent classes at the same time.
When using multiple inheritance it should be defensible to consider the
newly derived class an instantiation of both base classes. Otherwise,
i(composition) is more appropriate. In general, linear derivation (using only
one base class) is used much more frequently than multiple derivation. Good
class design dictates that a class should have a single, well described
responsibility and that principle often conflicts with multiple inheritance
where we can state that objects of class tt(Derived) are em(both) tt(Base1)
em(and) tt(Base2) objects.
But then, consider em(the) prototype of an object for which
multiple inheritance was used to its extreme: the
emi(Swiss army knife)! This object em(is) a knife, it em(is) a pair of
scissors, it em(is) a can-opener, it em(is) a corkscrew, it em(is) ....
The `Swiss army knife' is an extreme example of multiple inheritance. In
bf(C++) there em(are) various good arguments for using multiple inheritance as
well, without violating the `one class, one responsibility' principle. We
postpone those arguments until the link(next chapter)(POLYMORPHISM). The
current section concentrates on the technical details of constructing classes
using multiple inheritance.
How to construct a `Swiss army knife' in bf(C++)? First we need (at least)
two base classes. For example, let's assume we are designing a toolkit
allowing us to construct an instrument panel of an aircraft's cockpit. We
design all kinds of instruments, like an artificial horizon and an
altimeter. One of the components that is often seen in aircraft is a
em(nav-com set): a combination of a navigational beacon receiver (the `nav'
part) and a radio communication unit (the `com'-part). To define the nav-com
set, we start by designing the tt(NavSet) class (assume the existence of the
classes tt(Intercom, VHF_Dial) and tt(Message)):
verb( class NavSet
{
public:
NavSet(Intercom &intercom, VHF_Dial &dial);
size_t activeFrequency() const;
size_t standByFrequency() const;
void setStandByFrequency(size_t freq);
size_t toggleActiveStandby();
void setVolume(size_t level);
void identEmphasis(bool on_off);
};)
Next we design the class tt(ComSet):
verb( class ComSet
{
public:
ComSet(Intercom &intercom);
size_t frequency() const;
size_t passiveFrequency() const;
void setPassiveFrequency(size_t freq);
size_t toggleFrequencies();
void setAudioLevel(size_t level);
void powerOn(bool on_off);
void testState(bool on_off);
void transmit(Message &message);
};)
Using objects of this class we can receive messages, transmitted
though the tt(Intercom), but we can also em(transmit) messages using a
tt(Message) object that's passed to the tt(ComSet) object using its
tt(transmit) member function.
Now we're ready to construct our tt(NavCom) set:
verb( class NavComSet: public ComSet, public NavSet
{
public:
NavComSet(Intercom &intercom, VHF_Dial &dial);
};)
Done. Now we have defined a tt(NavComSet) which is em(both) a tt(NavSet)
em(and) a tt(ComSet): the facilities of both base classes are now
available in the derived class using multiple inheritance.
Please note the following:
itemization(
it() The keyword ti(public) is present before both base class names
(tt(NavSet) and tt(ComSet)). By default inheritance uses
emi(private derivation) and the keyword tt(public) must be repeated before
each of the base class specifications. Base classes are not required to use
the same derivation type. One base class could have tt(public) derivation and
another base class could use tt(private) derivation.
it() The multiply derived class tt(NavComSet) introduces no additional
functionality of its own, but merely combines two existing classes into a new
aggregate class. Thus, bf(C++) offers the possibility to simply sweep
multiple simple classes into one more complex class.
it() Here is the implementation of The tt(NavComSet) i(constructor):
verb(NavComSet::NavComSet(Intercom &intercom, VHF_Dial &dial)
:
ComSet(intercom),
NavSet(intercom, dial)
{})
The constructor requires no extra code: Its purpose is to activate
the constructors of its base classes. The order in which the base class
initializers hi(base class initializers: calling order)
hi(calling order of base class initializers) are called is em(not)
dictated by their calling order in the constructor's code, but by the ordering
of the base classes in the class interface.
it() The tt(NavComSet) class definition requires no additional data
members or member functions: here (and often) the inherited interfaces provide
all the required functionality and data for the multiply derived class to
operate properly.
)
Of course, while defining the base classes, we made life easy on ourselves
by strictly using different member function names. So, there is a function
tt(setVolume) in the tt(NavSet) class and a function tt(setAudioLevel) in the
tt(ComSet) class. A bit cheating, since we could expect that both units in
fact have a composed object tt(Amplifier), handling the volume setting. A
revised class might offer an tt(Amplifier &lifier() const) member function,
and leave it to the application to set up its own interface to the
amplifier. Alternatively, a revised class could define members for setting the
volume of either the tt(NavSet) or the tt(ComSet) parts.
In situations where two base classes offer identically named members
hi(identically named member functions)hi(member function: identically named)
special provisions need to be made to prevent ambiguity:
itemization(
it() The intended base class can explicitly be specified using the base
class name and i(scope resolution operator):
verb(NavComSet navcom(intercom, dial);
navcom.NavSet::setVolume(5); // sets the NavSet volume level
navcom.ComSet::setVolume(5); // sets the ComSet volume level)
it() The class interface is provided with member functions that can be
called unambiguously. These additional members are usually defined tt(inline):
verb(class NavComSet: public ComSet, public NavSet
{
public:
NavComSet(Intercom &intercom, VHF_Dial &dial);
void comVolume(size_t volume);
void navVolume(size_t volume);
};
inline void NavComSet::comVolume(size_t volume)
{
ComSet::setVolume(volume);
}
inline void NavComSet::navVolume(size_t volume)
{
NavSet::setVolume(volume);
})
it() If the tt(NavComSet) class is obtained from a third party, and cannot
be modified, a disambiguating i(wrapper class) may be used:
verb(class MyNavComSet: public NavComSet
{
public:
MyNavComSet(Intercom &intercom, VHF_Dial &dial);
void comVolume(size_t volume);
void navVolume(size_t volume);
};
inline MyNavComSet::MyNavComSet(Intercom &intercom, VHF_Dial &dial)
:
NavComSet(intercom, dial);
{}
inline void MyNavComSet::comVolume(size_t volume)
{
ComSet::setVolume(volume);
}
inline void MyNavComSet::navVolume(size_t volume)
{
NavSet::setVolume(volume);
})
)
|