mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-16 07:48:44 +01:00
updated through ch. 13, Inheritance
This commit is contained in:
parent
79569f76f0
commit
99805ea7de
14 changed files with 395 additions and 799 deletions
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
Early in the annotations() (cf. section ref(HIDING)) we encountered two
|
||||
important design principles when developing classes: em(data hiding) and
|
||||
em(encapsulation). Data hiding restricts control over an object's data to the
|
||||
members of its class, encapsulating is used to restrict access to the
|
||||
members of its class, encapsulation is used to restrict access to the
|
||||
functionality of objects. Both principles are invaluable tools for maintaining
|
||||
data integrity.
|
||||
|
||||
|
@ -15,13 +15,13 @@ requests sent to objects are handled. In a well-designed class its objects are
|
|||
in full control of their data.
|
||||
|
||||
Inheritance doesn't change these principles, nor does it change the way the
|
||||
tt(private) and tt(protected) keywords operate. A derived class does not have
|
||||
access to a base class's private section.
|
||||
`tt(private)' and `tt(protected)' keywords operate. A derived class does not
|
||||
have access to a base class's private section.
|
||||
|
||||
Sometimes this is a bit too restrictive. Consider a class implementing a
|
||||
random number generating tt(streambuf) (cf. chapter ref(IOStreams)). Such a
|
||||
tt(streambuf) can be used to construct an tt(istream irand), after which
|
||||
extractions from tt(irand) produces the next random number, like in the next
|
||||
extractions from tt(irand) produces series of random numbers, like in the next
|
||||
example in which 10 random numbers are generated using stream I/O:
|
||||
verb(
|
||||
RandBuf buffer;
|
||||
|
@ -43,8 +43,9 @@ number. RandBuf, therefore, operates as follows:
|
|||
it() It is passed in textual form to its base class tt(streambuf);
|
||||
it() The tt(istream) object extracts this random number, merely
|
||||
using tt(streambuf)'s interface;
|
||||
it() this process is repeated for subsequent random numbers;
|
||||
)
|
||||
(this process is repeated for subsequent random numbers).
|
||||
|
||||
Once tt(RandBuf) has stored the text representation of the next
|
||||
random number in some buffer, it must tell its base class (tt(streambuf))
|
||||
where to find the random number's characters. For this tt(streambuf) offers a
|
||||
|
|
|
@ -15,7 +15,7 @@ follows:
|
|||
Land::Land(size_t mass, size_t speed)
|
||||
{
|
||||
setMass(mass);
|
||||
setspeed(speed);
|
||||
setSpeed(speed);
|
||||
}
|
||||
)
|
||||
However, this implementation has several disadvantages.
|
||||
|
@ -27,7 +27,7 @@ called.
|
|||
it() Using the base class constructor only to reassign new values to its
|
||||
data members in the derived class constructor's body usually is inefficient,
|
||||
but sometimes sheer impossible as in situations where base class reference or
|
||||
const data members must be initialized. In those cases a specialized base
|
||||
tt(const) data members must be initialized. In those cases a specialized base
|
||||
class constructor must be used instead of the base class default constructor.
|
||||
)
|
||||
A derived class's base class may be initialized using a dedicated base
|
||||
|
|
|
@ -23,7 +23,7 @@ features are used, but others need to be shielded off. Consider the tt(stack)
|
|||
container: it is commonly implemented in-terms-of a tt(deque), returning
|
||||
tt(deque::back)'s value as tt(stack::top)'s value.
|
||||
|
||||
When using inheritance to implement an tt(is-a) relationship make sure to get
|
||||
When using inheritance to implement an em(is-a) relationship make sure to get
|
||||
the `direction of use' right: inheritance aiming at implementing an em(is-a)
|
||||
relationship should focus on the base class: the base class facilities aren't
|
||||
there to be used by the derived class, but the derived class facilities should
|
||||
|
|
|
@ -26,16 +26,15 @@ private members in the derived class. The derived class members may access
|
|||
all base class public and protected members but base class members cannot be
|
||||
used elsewhere.
|
||||
|
||||
Public derivation should be used to define an tt(is-a) relationship
|
||||
between a derived class and a base class: the derived class object
|
||||
em(is-a) base class object allowing the derived class object to be used
|
||||
polymorphically as a base class object in code expecting a base class
|
||||
object. Private inheritance is used in situations where a derived class object
|
||||
is defined in-terms-of the base class where composition cannot be
|
||||
used. There's little documented use for protected inheritance, but one could
|
||||
maybe encounter protected inheritance when defining a base class that is
|
||||
itself a derived class and needs to make its base class members available to
|
||||
classes derived from itself.
|
||||
Public derivation should be used to define an em(is-a) relationship
|
||||
between a derived class and a base class: the derived class object em(is-a)
|
||||
base class object allowing the derived class object to be used polymorphically
|
||||
as a base class object in code expecting a base class object. Private
|
||||
inheritance is used in situations where a derived class object is defined
|
||||
in-terms-of the base class where composition cannot be used. There's little
|
||||
documented use for protected inheritance, but one could maybe encounter
|
||||
protected inheritance when defining a base class that is itself a derived
|
||||
class making its base class members available to classes derived from it.
|
||||
|
||||
Combinations of inheritance types do occur. For example, when designing a
|
||||
stream-class it is usually derived from tt(std::istream) or
|
||||
|
|
|
@ -57,11 +57,11 @@ int main()
|
|||
|
||||
/*
|
||||
After providing 5 lines containing, respectively
|
||||
alfa, bravo, charlie, delta, echo
|
||||
alpha, bravo, charley, delta, echo
|
||||
the program displays:
|
||||
destructor: alfa
|
||||
destructor: alpha
|
||||
destructor: bravo
|
||||
destructor: charlie
|
||||
destructor: charley
|
||||
destructor: delta
|
||||
destructor: echo
|
||||
*/
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
Up to now, a class has 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.
|
||||
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.
|
||||
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
|
||||
|
@ -19,10 +19,11 @@ multiple inheritance was used to its extreme: the
|
|||
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) some good reasons, not violating the `one class, one
|
||||
responsibility' principle that is covered in the
|
||||
link(next chapter)(POLYMORPHISM). In this section the technical details of
|
||||
constructing classes using multiple inheritance are discussed.
|
||||
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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
pointer variable:
|
||||
verb(
|
||||
Land land(1200, 130);
|
||||
Car auto(500, 75, "Daf");
|
||||
Car car(500, 75, "Daf");
|
||||
Truck truck(2600, 120, "Mercedes", 6000);
|
||||
Vehicle *vp;
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ pointer variable:
|
|||
the derived classes to the tt(Vehicle) pointer:
|
||||
verb(
|
||||
vp = &land;
|
||||
vp = &auto;
|
||||
vp = &car;
|
||||
vp = &truck;
|
||||
)
|
||||
Each of these assignments is acceptable. However, an
|
||||
|
|
|
@ -27,10 +27,9 @@ member:
|
|||
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;
|
||||
}
|
||||
Car(tractor_mass, speed, name),
|
||||
d_mass(tractor_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).
|
||||
|
@ -135,7 +134,7 @@ size_t Truck::mass() const
|
|||
)
|
||||
|
||||
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
|
||||
this class design. Since a truck is conceived of as a combination of a
|
||||
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).
|
||||
|
|
|
@ -73,7 +73,7 @@ tt(Vehicle):
|
|||
Land();
|
||||
Land(size_t mass, size_t speed);
|
||||
|
||||
void setspeed(size_t speed);
|
||||
void setSpeed(size_t speed);
|
||||
size_t speed() const;
|
||||
};
|
||||
)
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue