File: implementation.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 (130 lines) | stat: -rw-r--r-- 6,088 bytes parent folder | download | duplicates (4)
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
This section briefly describes how hi(polymorphism: how) polymorphism is
implemented in bf(C++).  It is not necessary to understand how polymorphism is
implemented if you just want to em(use) polymorphism. However, we think it's
nice to know how polymorphism is possible. Also, knowing how polymorphism is
implemented clarifies why there is a (small) penalty to using polymorphism in
terms of memory usage and efficiency.

The fundamental idea behind polymorphism is that the compiler does not know
which function to call at compile-time. The appropriate function is
selected at run-time. That means that the address of the function must be
available somewhere, to be looked up prior to the actual call. This
`somewhere' place must be accessible to the object in question. So when a
tt(Vehicle *vp) points to a tt(Truck) object, then tt(vp->mass()) calls
tt(Truck)'s member function. The address of this function is obtained through
the actual object to which tt(vp) points.

Polymorphism is commonly implemented as follows: an object containing virtual
member functions also contains, usually as its first data member a
 i(hidden data member), pointing to an array containing the addresses of the
class's virtual member functions. The hidden data member is usually called the
emi(vpointer), the array of virtual member function addresses the emi(vtable).

The class's vtable is shared by all objects of that class.  The overhead of
polymorphism in terms of i(memory consumption) is therefore:
    itemization(
    it() one vpointer data member per object pointing to:
    it() one vtable per class.
    )
    Consequently, a statement like tt(vp->mass) first inspects the hidden
data member of the object pointed to by tt(vp). In the case of the vehicle
classification system, this data member points to a table containing two
addresses: one pointer to the function tt(mass) and one pointer to the
function tt(setMass) (three pointers if the class also defines (as it
should) a virtual destructor). The actually called function is determined from
this table.

The internal organization of the objects having virtual functions is
illustrated in fig(ImplementationFigure) and fig(CaumonFigure)
(originals provided by url(Guillaume Caumon)
    (mailto:Guillaume.Caumon@ensg.inpl-nancy.fr)).

        figure(polymorphism/implementation)
        (Internal organization objects when virtual functions are defined.)
        (ImplementationFigure)

        figure(polymorphism/caumon)
        (Complementary figure, provided by Guillaume Caumon)
        (CaumonFigure)

    As shown by fig(ImplementationFigure) and fig(CaumonFigure),
objects potentially using virtual member functions must have one (hidden) data
member to address a table of function pointers. The objects of the classes
tt(Vehicle) and tt(Car) both address the same table. The class tt(Truck),
however, overrides tt(mass). Consequently, tt(Truck) needs its own vtable.

A small complication arises when a class is derived from multiple base
classes, each defining virtual functions. Consider the following example:
        verb(    class Base1
    {
        public:
            virtual ~Base1();
            void fun1();        // calls vOne and vTwo
        private:
            virtual void vOne();
            virtual void vTwo();
    };
    class Base2
    {
        public:
            virtual ~Base2();
            void fun2();        // calls vThree
        private:
            virtual void vThree();
    };
    class Derived: public Base1, public Base2
    {
        public:
            ~Derived() override;
        private:
            void vOne() override;
            void vThree() override;
    };)

In the example tt(Derived) is multiply derived from tt(Base1) and tt(Base2),
each supporting virtual functions. Because of this, tt(Derived) also has
virtual functions, and so tt(Derived) has a tt(vtable) allowing a base class
pointer or reference to access the proper virtual member.

When tt(Derived::fun1) is called (or a tt(Base1) pointer pointing to a
tt(Derived) object calls tt(fun1)) then tt(fun1) calls tt(Derived::vOne) and
tt(Base1::vTwo). Likewise, when tt(Derived::fun2) is called
tt(Derived::vThree) is called.

The complication
 hi(multiple inheritance: vtable)hi(vtable: and multiple inheritance) occurs
with tt(Derived)'s vtable. When tt(fun1) is called its class type determines
the vtable to use and hence which virtual member to call. So when tt(vOne) is
called from tt(fun1), it is presumably the second entry in tt(Derived)'s
vtable, as it must match the second entry in tt(Base1)'s vtable. However, when
tt(fun2) calls tt(vThree) it apparently is also the second entry in
tt(Derived)'s vtable as it must match the second entry in tt(Base2)'s vtable.

Of course this cannot be realized by a single vtable. Therefore, when multiple
inheritance is used (each base class defining virtual members) another
approach is followed to determine which virtual function to call. In this
situation (cf. figure fig(MultiVtableFig)) the class tt(Derived) receives
em(two) tt(vtable)s, one for each of its base classes and each tt(Derived)
class object harbors em(two) hidden vpointers, each one pointing to its
corresponding vtable.

        figure(polymorphism/multivtable)
        (Vtables and vpointers with multiple base classes)
        (MultiVtableFig)

Since base class pointers, base class references, or base class interface
members unambiguously refer to one of the base classes the compiler can
determine which vpointer to use.

The following therefore holds true for classes multiply derived from base
classes offering virtual member functions:
    itemization(
    it() the derived class defines a vtable for each of its base classes
offering virtual members;
    it() Each derived class object contains as many hidden vpointers as it has
vtables.
    it() Each of a derived class object's vpointers points to a unique vtable
and the vpointer to use is determined by the class type of the base class
pointer, the base class reference, or the base class interface function that
is used.
    )