File: function.yo

package info (click to toggle)
c%2B%2B-annotations 13.02.02-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,576 kB
  • sloc: cpp: 25,297; makefile: 1,523; ansic: 165; sh: 126; perl: 90; fortran: 27
file content (192 lines) | stat: -rw-r--r-- 8,568 bytes parent folder | download | duplicates (2)
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
By default the behavior of a member function called via a pointer or reference
is determined by the implementation of that function in the pointer's or
reference's class. E.g., a tt(Vehicle *) activates tt(Vehicle)'s member
functions, even when pointing to an object of a derived class. This is known
as as em(early) or
    hi(early binding) hi(static binding) em(static) binding: the function to
call is determined at
 i(compile-time). In bf(C++) em(late)
 hi(late binding) or hi(dynamic binding) em(dynamic) binding is realized using
em(virtual member functions).

A member function becomes a i(virtual member function) when its declaration
starts with the keyword ti(virtual). It is stressed once again that in
bf(C++), different from several other object oriented languages, this is
em(not) the default situation. By default em(static) binding is used.

Once a function is declared tt(virtual) in a base class, it remains virtual in
all derived classes. The keyword tt(virtual) should not be mentioned for
members in derived classes which are declared virtual in base classes. In
derived classes those members should be provided with the ti(override)
indicator, allowing the compiler to verify that you're indeed referring to an
existing virtual member function.

In the vehicle classification system (see section ref(VehicleSystem)), let's
concentrate on the members tt(mass) and tt(setMass). These members define the
emi(user interface) of the class tt(Vehicle). What we would like to accomplish
is that this user interface can be used for tt(Vehicle) and for any class
inheriting from tt(Vehicle), since objects of those classes are themselves
also tt(Vehicles). 

If we can define the user interface of our base class (e.g., tt(Vehicle)) such
that it remains usable irrespective of the classes we derive from tt(Vehicle)
our software achieves an enormous reusability: we design our software around
tt(Vehicle's) user interface, and our software will also properly function for
derived classes. Using plain inheritance doesn't accomplish this. If we define 
        verb(    std::ostream &operator<<(std::ostream &out, Vehicle const &vehicle)
    {
        return out << "Vehicle's mass is " << vehicle.mass() << " kg.";
    })

and tt(Vehicle's) member tt(mass) returns 0, but tt(Car's) member
tt(mass) returns 1000, then twice a mass of 0 is reported when the following
program is executed:
        verb(    int main()
    {
        Vehicle vehicle;
        Car vw{ 1000, 160, "Golf" };

        cout << vehicle << '\n' << vw << '\n';
    })

We've defined an overloaded insertion operator, but since it only knows
about tt(Vehicle's) user interface, `tt(cout << vw)' will use tt(vw's
Vehicle's) user interface as well, thus displaying a mass of 0. 

    Reusability is enhanced if we add a em(redefinable interface) to the base
class's interface. A redefinable interface allows derived classes to fill in
their own implementation, without affecting the user interface. At the same
time the user interface will behave according to the derived class's wishes,
and not just to the base class's default implementation.

    Members of the reusable interface should be declared in the class's
private sections: conceptually they merely belong to their own classes
(cf. section ref(INHERITWHY)). In the base class these members should be
declared tt(virtual). These members can be redefined (overridden) by derived
classes, and should there be provided with tt(override) indicators.

We keep our user interface (tt(mass)), and add the redefinable member 
tt(vmass) to tt(Vehicle's) interface:
        verb(    class Vehicle
    {
        public:
            size_t mass() const;
            size_t si_mass() const;    // see below

        private:
            virtual size_t vmass() const;
    };)

Separating the user interface from the redefinable interface is a sensible
thing to do. It allows us to fine-tune the user interface (only one point of
maintenance), while at the same time allowing us to standardize the expected
behavior of the members of the redefinable interface. E.g., in many countries
the International system of units is used, using the kilogram as the unit for
mass. Some countries use other units (like the em(lbs): 1 kg being
approx. 2.2046 lbs). By separating the user interface from the redefinable
interface we can use one standard for the redefinable interface, and keep the
flexibility of transforming the information em(ad-lib) in the user
interface. 

    Just to maintain a clean separation of user- and redefinable interface we
might consider adding another accessor to tt(Vehicle), providing the
tt(si_mass), simply implemented like this:
        verb(    size_t Vehicle::si_mass() const
    {
        return vmass();
    })

If tt(Vehicle) supports a member tt(d_massFactor) then its tt(mass) member can
be implemented like this:
        verb(    size_t Vehicle::mass()
    {
        return d_massFactor * si_mass();
    })

tt(Vehicle) itself could define tt(vmass) so that it returns a token
value. E.g.,
        verb(    size_t Vehicle::vmass()
    {
        return 0;
    })

Now let's have a look at the class tt(Car). It is derived from
tt(Vehicle), and it inherits tt(Vehicle's) user interface. It also has a data
member tt(size_t d_mass), and it implements its own reusable interface:
        verb(    class Car: public Vehicle
    {
        ...
        private:
            size_t vmass() override;
    })

If tt(Car) constructors require us to specify the car's mass (stored
in tt(d_mass)), then tt(Car) simply implements its tt(vmass) member like
this:
        verb(    size_t Car::vmass() const
    {
        return d_mass;
    })

The class tt(Truck), inheriting from tt(Car) needs two mass values: the
tractor's mass and the trailer's mass. The tractor's mass is passed to its
tt(Car) base class, the trailor's mass is passed to its tt(Vehicle d_trailor)
data member. tt(Truck), too, overrides tt(vmass), this time returning the sum
of its tractor and trailor masses:
        verb(    size_t Truck::vmass() const
    {
        return Car::si_mass() + d_trailer.si_mass();
    })

Once a class member has been declared tt(virtual) it becomes
a virtual member in all derived classes, whether or not these members are
provided with the tt(override) indicator. But tt(override) em(should) be used,
as it allows to compiler to catch typos when writing down the derived class
interface. 

    A member function may be declared tt(virtual) em(anywhere) in a
    i(class hierarchy), but this probably defeats the underlying polymorphic
class design, as the original base class is no longer capable of completely
covering the redefinable interfaces of derived classes. If, e.g, tt(mass) is
declared virtual in tt(Car), but not in tt(Vehicle), then the specific
characteristics of virtual member functions would only be available for
tt(Car) objects and for objects of classes derived from tt(Car). For a
tt(Vehicle) pointer or reference static binding would remain to be used.  

    The effect of late binding (polymorphism) is illustrated below:
        verb(    void showInfo(Vehicle &vehicle)
    {
        cout << "Info: " << vehicle << '\n';
    }

    int main()
    {
        Car car(1200);            // car with mass 1200
        Truck truck(6000, 115,      // truck with cabin mass 6000, 
              "Scania", 15000);     // speed 115, make Scania, 
                                    // trailer mass 15000

        showInfo(car);             // see (1) below
        showInfo(truck);            // see (2) below

        Vehicle *vp = &truck;
        cout << vp->speed() << '\n';// see (3) below
    })

Now that tt(mass) is defined tt(virtual), late binding is used:
    itemization(
    it() at (1), tt(Car)'s mass is displayed;
    it() at (2) tt(Truck)'s mass is displayed;
    it() at (3) a syntax error is generated. The member
        tt(speed) is not a member of tt(Vehicle), and hence not callable via
        a tt(Vehicle*).
    )
    The example illustrates that when a pointer to a class is used em(only the
members of that class can be called).  A member's tt(virtual) characteristic
only influences the type of binding (early vs. late), not the set of member
functions that is visible to the pointer.

Through virtual members derived classes may redefine the behavior performed by
functions called from base class members or from pointers or references to
base class objects. This redefinition of base class members by derived classes
is called hi(member: overriding)emi(overriding members).