~ubuntu-branches/ubuntu/trusty/c++-annotations/trusty

« back to all changes in this revision

Viewing changes to yo/polymorphism/function.yo

  • Committer: Package Import Robot
  • Author(s): tony mancill, Frank B. Brokken, tony mancill
  • Date: 2014-01-18 08:55:27 UTC
  • mfrom: (1.1.26)
  • Revision ID: package-import@ubuntu.com-20140118085527-ph5upqqn423cmnmz
Tags: 9.8.0-1
[ Frank B. Brokken ]
* New upstream release, adds a section about static polymorphism, removes
  the concrete/a2x section, and removes C++11 indicators from the section
  headers. 

[ tony mancill ]
* Use debhelper 9.
* Tweak build-deps for g++ to use >= 4.8.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
em(not) the default situation. By default em(static) binding is used.
16
16
 
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
19
 
derived classes.
20
 
 
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.
 
21
 
 
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
 
27
also tt(Vehicles). 
 
28
 
 
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 
 
34
        verb(
 
35
    std::ostream &operator<<(std::ostream &out, Vehicle const &vehicle)
 
36
    {
 
37
        return out << "Vehicle's mass is " << vehicle.mass() << " kg.";
 
38
    }
 
39
        )
 
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
 
42
program is executed:
 
43
        verb(
 
44
    int main()
 
45
    {
 
46
        Vehicle vehicle;
 
47
        Car vw(1000);
 
48
 
 
49
        cout << vehicle << '\n' << vw << endl;
 
50
    }
 
51
        )
 
52
 
 
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. 
 
56
 
 
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.
 
62
 
 
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.
 
68
 
 
69
We keep our user interface (tt(mass)), and add the redefinable member 
 
70
tt(vmass) to tt(Vehicle's) interface:
26
71
        verb(
27
72
    class Vehicle
28
73
    {
29
74
        public:
30
 
            virtual int mass() const;
31
 
    };
32
 
    class Truck: // inherited from Vehicle through Auto and Land
33
 
    {
34
 
        // not altered
35
 
    };
36
 
    int Vehicle::mass() const
 
75
            int mass() const;
 
76
            int si_mass() const;    // see below
 
77
 
 
78
        private:
 
79
            virtual vmass() const;
 
80
    };
 
81
            )
 
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
 
91
interface. 
 
92
 
 
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:
 
96
        verb(
 
97
    int Vehicle::si_mass() const
 
98
    {
 
99
        return vmass();
 
100
    }
 
101
        )
 
102
 
 
103
If tt(Vehicle) supports a member tt(d_massFactor) then its tt(mass) member can
 
104
be implemented like this:
 
105
        verb(
 
106
    int Vehicle::mass()
 
107
    {
 
108
        return d_massFactor * si_mass();
 
109
    }
 
110
        )
 
111
    tt(Vehicle) itself could define tt(vmass) so that it returns a token
 
112
value. E.g.,
 
113
        verb(
 
114
    int Vehicle::vmass()
 
115
    {
 
116
        return 0;
 
117
    }
 
118
        )
 
119
 
 
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:
 
123
        verb(
 
124
    class Car: public Vehicle
 
125
    {
 
126
        ...
 
127
        private:
 
128
            int vmass() override;
 
129
    }
 
130
        )
 
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
 
133
this:
 
134
        verb(
 
135
    int Car::vmass() const
37
136
    {
38
137
        return d_mass;
39
138
    }
40
 
 
41
 
    int Truck::mass() const
42
 
    {
43
 
        return Auto::mass() + d_trailer_wt;
44
 
    }
45
 
        )
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:
57
 
        verb(
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
 
139
        )
 
140
 
 
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:
 
146
        verb(
 
147
    int Truck::vmass() const
 
148
    {
 
149
        return Car::si_mass() + d_trailer.si_mass();
 
150
    }
 
151
        )
 
152
 
 
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
 
157
interface. 
 
158
 
 
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.  
 
167
 
 
168
    The effect of late binding (polymorphism) is illustrated below:
 
169
        verb(
 
170
    void showInfo(Vehicle &vehicle)
 
171
    {
 
172
        cout << "Info: " << vehicle << '\n';
 
173
    }
62
174
 
63
175
    int main()
64
176
    {
65
 
        vp = &v;                            // see (1) below
66
 
        cout << vp->mass() << '\n';
67
 
 
68
 
        vp = &t;                            // see (2) below
69
 
        cout << vp->mass() << '\n';
70
 
 
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
 
181
 
 
182
        showInfo(car);             // see (1) below
 
183
        showInfo(truck);            // see (2) below
 
184
 
 
185
        Vehicle *vp = &truck;
 
186
        cout << vp->speed() << '\n';// see (3) below
72
187
    }
73
188
        )
74
189
    Now that tt(mass) is defined tt(virtual), late binding is used:
75
190
    itemization(
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
80
 
a tt(Vehicle*).
 
194
        tt(speed) is not a member of tt(Vehicle), and hence not callable via
 
195
        a tt(Vehicle*).
81
196
    )
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
86
 
to the pointer.
 
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.
87
201
 
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).
 
206
 
 
207