cppannotations/annotations/yo/inheritance/redefining.yo
Frank B. Brokken 31097cd573 typo
2016-11-17 14:33:26 +01:00

175 lines
6.4 KiB
Text

Derived classes may redefine base class members. Let's assume that a vehicle
classification system must also cover trucks, consisting of two parts: the
front part, the tractor, pulls the rear part, the trailer. Both the
tractor and the trailer have their own mass, and the tt(mass) function
should return the combined mass.
The definition of a tt(Truck) starts with a class definition. Our initial
tt(Truck) class is derived from tt(Car) but it is then expanded to hold one
more tt(size_t) field representing the additional mass information. Here we
choose to represent the mass of the tractor in the tt(Car) class and to store
the mass of of full truck (tractor + trailer) in its own tt(d_mass) data
member:
verb(
class Truck: public Car
{
size_t d_mass;
public:
Truck();
Truck(size_t tractor_mass, size_t speed, char const *name,
size_t trailer_mass);
void setMass(size_t tractor_mass, size_t trailer_mass);
size_t mass() const;
};
Truck::Truck(size_t tractor_mass, size_t speed, char const *name,
size_t trailer_mass)
:
Car(tractor_mass, speed, name)
{
d_mass = trailer_mass + trailer_mass;
}
)
Note that the class tt(Truck) now contains two functions already
present in the base class tt(Car): tt(setMass) and tt(mass).
itemization(
it() The redefinition of tt(setMass) poses no problems: this
function is simply redefined to perform actions which are specific to a
tt(Truck) object.
it() Redefining tt(setMass), however,
hi(hiding: base class members) hi(base class: hiding members) em(hides)
tt(Car::setMass). For a tt(Truck) only the tt(setMass) function having
two tt(size_t) arguments can be used.
it() The tt(Vehicle)'s tt(setMass) function remains available for a
tt(Truck), but it must now be
hi(member function: called explicitly) called em(explicitly), as
tt(Car::setMass) is hidden from view. This latter function is hidden,
even though tt(Car::setMass) has only one tt(size_t) argument. To implement
tt(Truck::setMass) we could write:
verb(
void Truck::setMass(size_t tractor_mass, size_t trailer_mass)
{
d_mass = tractor_mass + trailer_mass;
Car::setMass(tractor_mass); // note: Car:: is required
}
)
it() Outside of the class tt(Car::setMass) is
accessed using the i(scope resolution operator). So, if a tt(Truck truck) needs
to set its tt(Car) mass, it must use
verb(
truck.Car::setMass(x);
)
it() An alternative to using the scope resolution operator is to add a
member having the same function prototype as the base class member to the
derived class's interface. This derived class member could be implemented
inline to call the base class member. E.g., we add the following member to the
tt(class Truck):
verb(
// in the interface:
void setMass(size_t tractor_mass);
// below the interface:
inline void Truck::setMass(size_t tractor_mass)
{
(d_mass -= Car::mass()) += tractor_mass;
Car::setMass(tractor_mass);
}
)
Now the single argument tt(setMass) member function can be used by
tt(Truck) objects without using the scope resolution operator. As the
function is defined inline, no overhead of an additional function call is
involved.
it() hi(derived class: using declaration)hi(using: in derived classes) To
prevent hiding the base class members a tt(using) declaration may be added to
the derived class interface. The relevant section of tt(Truck)'s class
interface then becomes:
verb(
class Truck: public Car
{
public:
using Car::setMass;
void setMass(size_t tractor_mass, size_t trailer_mass);
};
)
A using declaration imports (all overloaded versions of) the mentioned
member function directly into the derived class's interface. If a base class
member has a signature that is identical to a derived class member then
compilation fails (a tt(using Car::mass) declaration cannot be added to
tt(Truck)'s interface). Now code may use tt(truck.setMass(5000)) as well as
tt(truck.setMass(5000, 2000)).
Using declarations obey access rights. To prevent non-class members from
using tt(setMass(5000)) without a scope resolution operator but allowing
derived class members to do so the tt(using Car::setMass) declaration
should be put in the class tt(Truck)'s private section.
it() The function tt(mass) is also already defined in tt(Car), as
it was inherited from tt(Vehicle). In this case, the class tt(Truck)
em(redefines) this member function to return the truck's full mass:
verb(
size_t Truck::mass() const
{
return d_mass;
}
)
)
Example:
verb(
int main()
{
Land vehicle(1200, 145);
Truck lorry(3000, 120, "Juggernaut", 2500);
lorry.Vehicle::setMass(4000);
cout << '\n' << "Tractor weighs " <<
lorry.Vehicle::mass() << '\n' <<
"Truck + trailer weighs " << lorry.mass() << '\n' <<
"Speed is " << lorry.speed() << '\n' <<
"Name is " << lorry.name() << '\n';
}
)
The class tt(Truck) was derived from tt(Car). However, one might question
this class design. Since a truck is conceived of as a combination of an
tractor and a trailer it is probably better defined using a mixed design,
using inheritance for the tractor part (inheriting from tt(Car), and
composition for the trailer part).
This redesign changes our point of view from a tt(Truck) em(being) a tt(Car)
(and some strangely added data members) to a tt(Truck) still em(being) an
tt(Car) (the tractor) and em(containing) a tt(Vehicle) (the trailer).
tt(Truck)'s interface is now very specific, not requiring users to study
tt(Car)'s and tt(Vehicle)'s interfaces and it opens up possibilities for
defining `road trains': tractors towing multiple trailers. Here is an example
of such an alternate class setup:
verb(
class Truck: public Car // the tractor
{
Vehicle d_trailer; // use vector<Vehicle> for road trains
public:
Truck();
Truck(size_t tractor_mass, size_t speed, char const *name,
size_t trailer_mass);
void setMass(size_t tractor_mass, size_t trailer_mass);
void setTractorMass(size_t tractor_mass);
void setTrailerMass(size_t trailer_mass);
size_t tractorMass() const;
size_t trailerMass() const;
// consider:
Vehicle const &trailer() const;
};
)