15
15
em(not) the default situation. By default em(static) binding is used.
17
17
Once a function is declared tt(virtual) in a base class, it remains virtual in
18
all derived classes; even when the keyword tt(virtual) is not repeated in
21
In the vehicle classification system (see section ref(VehicleSystem)) the two
22
member functions tt(mass) and tt(setMass) might be declared
23
tt(virtual). Concentrating on tt(mass), the relevant sections of the class
24
definitions of the class tt(Vehicle) and tt(Truck) are shown below. Also, we
25
show the implementations of the member function tt(mass):
18
all derived classes. The keyword tt(virtual) should not be mentioned with
19
members declared virtual in the base class. In derived classes those members
20
should be provided with the ti(override) indicator.
22
In the vehicle classification system (see section ref(VehicleSystem)), let's
23
concentrate on the members tt(mass) and tt(setMass). These members define the
24
emi(user interface) of the class tt(Vehicle). What we would like to accomplish
25
is that this user interface can be used for tt(Vehicle) and for any class
26
inheriting from tt(Vehicle), since objects of those classes are themselves
29
If we can define the user interface of our base class (e.g., tt(Vehicle)) such
30
that it remains usable irrespective of the classes we derive from tt(Vehicle)
31
our software achieves an enormous reusability: we design or software around
32
tt(Vehicle's) user interface, and our software will also properly function for
33
derived classes. Using plain inheritance doesn't accomplish this. If we define
35
std::ostream &operator<<(std::ostream &out, Vehicle const &vehicle)
37
return out << "Vehicle's mass is " << vehicle.mass() << " kg.";
40
and tt(Vehicle's) member tt(mass) returns 0, but tt(Car's) member
41
tt(mass) returns 1000, then twice a mass of 0 is reported when the following
49
cout << vehicle << '\n' << vw << endl;
53
We've defined an overloaded insertion operator, but since it only knows
54
about tt(Vehicle's) user interface, `tt(cout << vw)' will use tt(vw's
55
Vehicle's) user interface as well, thus displaying a mass of 0.
57
Reusablility is enhanced if we add a em(redefinable interface) to the base
58
class's interface. A redefinable interface allows derived classes to fill in
59
their own implementation, without affecting the user interface. At the same
60
time the user interface will behave according to the derived class's wishes,
61
and not just to the base class's default implementation.
63
Members of the reusable interface should be declared in the class's
64
private sections: conceptually they merely belong to their own classes
65
(cf. section ref(INHERITWHY)). In the base class these members should be
66
declared tt(virtual). These members can be redefined (overridden) by derived
67
classes, and should there be provided with tt(override) indicators.
69
We keep our user interface (tt(mass)), and add the redefinable member
70
tt(vmass) to tt(Vehicle's) interface:
30
virtual int mass() const;
32
class Truck: // inherited from Vehicle through Auto and Land
36
int Vehicle::mass() const
76
int si_mass() const; // see below
79
virtual vmass() const;
82
Separating the user interface from the redefinable interface is a sensible
83
thing to do. It allows us to fine-tune the user interface (only one point of
84
maintenance), while at the same time allowing us to standardize the expected
85
behavior of the members of the redefinable interface. E.g., in many countries
86
the International system of units is used, using the kilogram as the unit for
87
mass. Some countries use other units (like the em(lbs): 1 kg being
88
approx. 2.2046 lbs). By separating the user interface from the redefinable
89
interface we can use one standard for the redefinable interface, and keep the
90
flexibility of transforming the information em(ad-lib) in the user
93
Just to maintain a clean separation of user- and redefinable interface we
94
might consider adding another accessor to tt(Vehicle), providing the
95
tt(si_mass), simply implemented like this:
97
int Vehicle::si_mass() const
103
If tt(Vehicle) supports a member tt(d_massFactor) then its tt(mass) member can
104
be implemented like this:
108
return d_massFactor * si_mass();
111
tt(Vehicle) itself could define tt(vmass) so that it returns a token
120
Now let's have a look at the class tt(Car). It is derived from
121
tt(Vehicle), and it inherits tt(Vehicle's) user interface. It also has a data
122
member tt(int d_mass), and it implements its own reusable interface:
124
class Car: public Vehicle
128
int vmass() override;
131
If tt(Car) constructors require us to specify the car's mass (stored
132
in tt(d_mass)), then tt(Car) simply implements its tt(vmass) member like
135
int Car::vmass() const
41
int Truck::mass() const
43
return Auto::mass() + d_trailer_wt;
46
The keyword tt(virtual) em(only) appears in the (tt(Vehicle)) base
47
class. There is no need (but there is also no i(penalty)) to repeat it in
48
derived classes. Once a class member has been declared tt(virtual) it becomes
49
a virtual member in all derived classes. A member function may be
50
declared tt(virtual) em(anywhere) in a
51
i(class hierarchy). The compiler is perfectly happy if tt(mass) is
52
declared tt(virtual) in tt(Auto), rather than in tt(Vehicle). The specific
53
characteristics of virtual member functions would then only be available for
54
tt(Auto) objects and for objects of classes derived from tt(Auto). For a
55
tt(Vehicle) pointer static binding would remain to be used. The effect of
56
late binding is illustrated below:
58
Vehicle v(1200); // vehicle with mass 1200
59
Truck t(6000, 115, // truck with cabin mass 6000, speed 115,
60
"Scania", 15000); // make Scania, trailer mass 15000
61
Vehicle *vp; // generic vehicle pointer
141
The class tt(Truck), inheriting from tt(Car) needs two mass values: the
142
tractor's mass and the trailer's mass. The tractor's mass is passed to its
143
tt(Car) base class, the trailor's mass is passed to its tt(Vehicle d_trailor)
144
data member. tt(Truck), too, overrides tt(vmass), this time returning the sum
145
of its tractor and trailor masses:
147
int Truck::vmass() const
149
return Car::si_mass() + d_trailer.si_mass();
153
Once a class member has been declared tt(virtual) it becomes
154
a virtual member in all derived classes, whether or not these members are
155
provided with the tt(override) indicator. But tt(override) em(should) be used,
156
as it allows to compiler to catch typos when writing down the derived class
159
A member function may be declared tt(virtual) em(anywhere) in a
160
i(class hierarchy), but this probably defeats the underlying polymorphic
161
class design, as the original base class is no longer capable of completely
162
covering the redefinable interfaces of derived classes. If, e.g, tt(mass) is
163
declared virtual in tt(Car), but not in tt(Vehicle), then the specific
164
characteristics of virtual member functions would only be available for
165
tt(Car) objects and for objects of classes derived from tt(Car). For a
166
tt(Vehicle) pointer or reference static binding would remain to be used.
168
The effect of late binding (polymorphism) is illustrated below:
170
void showInfo(Vehicle &vehicle)
172
cout << "Info: " << vehicle << '\n';
65
vp = &v; // see (1) below
66
cout << vp->mass() << '\n';
68
vp = &t; // see (2) below
69
cout << vp->mass() << '\n';
71
cout << vp->speed() << '\n'; // see (3) below
177
Car car(1200); // car with mass 1200
178
Truck truck(6000, 115, // truck with cabin mass 6000,
179
"Scania", 15000); // speed 115, make Scania,
180
// trailer mass 15000
182
showInfo(car); // see (1) below
183
showInfo(truck); // see (2) below
185
Vehicle *vp = &truck;
186
cout << vp->speed() << '\n';// see (3) below
74
189
Now that tt(mass) is defined tt(virtual), late binding is used:
76
it() at (1), tt(Vehicle::mass) is called.
77
it() at (2) tt(Truck::mass) is called.
191
it() at (1), tt(Car)'s mass is displayed;
192
it() at (2) tt(Truck)'s mass is displayed;
78
193
it() at (3) a syntax error is generated. The member
79
tt(speed) is no member of tt(Vehicle), and hence not callable via
194
tt(speed) is not a member of tt(Vehicle), and hence not callable via
82
197
The example illustrates that when a pointer to a class is used em(only the
83
members of that class can be called). These functions may or may not be
84
tt(virtual). A member's tt(virtual) characteristic only influences the type of
85
binding (early vs. late), not the set of member functions that is visible
198
members of that class can be called). A member's tt(virtual) characteristic
199
only influences the type of binding (early vs. late), not the set of member
200
functions that is visible to the pointer.
88
202
Through virtual members derived classes may redefine the behavior performed by
89
203
functions called from base class members or from pointers or references to
90
204
base class objects. This redefinition of base class members by derived classes
91
205
is called hi(members: overriding)emi(overriding members).