File: examplestaticpoly.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 (173 lines) | stat: -rw-r--r-- 8,612 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
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
With static polymorphism a class template takes the role of a base class in
dynamic polymorphism. This class template declares several members, which are
comparable to members of a polymorphic base class: they are either support
members or they call members that are overridden in derived classes.

In the context of dynamic polymorphism these overridable members are the base
class's virtual members. In the context of static polymorphism there are no
virtual members. Instead, the statically polymorphic base class (referred to
as the `base class' below) declares a em(template type parameter) (referred to
as the `derived class type' below). Next, the base class's interfacing members
call members of the derived class type.

Here is a simple example: a class template acting as a base class. Its public
interface consists of one member. But different from dynamic polymorphism
there's no reference in the class's interface to any member showing
polymorphic behavior (i.e, no `virtual' members are declared):
        verb(    template <class Derived>
    struct Base
    {
        void interface();
    })

Let's have a closer look at the member `tt(interface)'. This member is called
by functions receiving a reference or pointer to the base class, but it may
call members that must be available in the derived class type at the point
where tt(interface) is called. Before we can call members of the derived class
type an object of the derived class type must be available. This object is
obtained through inheritance. The derived class type is going to be derived
from the base class. Thus tt(Base's this) pointer is also tt(Derived's this)
pointer.

Forget about polymorphism for a second: when we have a tt(class Derived:
public Base) then (because of inheritance) a tt(static_cast<Derived *>) can be
used to cast a tt(Base *) to a tt(Derived) object. A tt(dynamic_cast) of
course doesn't apply, as we don't use dynamic polymorphism. But a
tt(static_cast) is appropriate since our tt(Base *) em(does) in fact point to
a tt(Derived) class object.

So, to call a tt(Derived) class member from inside tt(interface) we
can use the following implementation (remember that tt(Base) is a base class
of tt(Derived)):
        verb(    template<class Derived>
    void Base<Derived>::interface()
    {
        static_cast<Derived *>(this)->polymorphic();
    })

It's remarkable that, when the compiler is given this implementation it
cannot determine whether tt(Derived) is em(really) derived from
tt(Base). Neither can it determine whether the class tt(Derived) indeed offers
a member tt(polymorphic). The compiler simply em(assumes) this to be true. If
so, then the provided implementation is syntactically correct. One of the
key characteristics of using templates is that the implementation's viability
is eventually determined at the function's point of instantiation (cf. section
ref(TEMPFUNINST)). At that point the compiler will verify that, e.g., the
function tt(polymorphic) really is available.

    Thus, in order to use the above scheme we must ensure that 
        itemization(
        it() derived class type is actually derived from the base class and
        it() that the derived class type defines a member
            `tt(polymorphic)'. 
        )
    The first requirement is satisfied by using the 
        emi(curiously recurring template pattern):
        verb(    class First: public Base<First>)

In this curious pattern the class tt(First) is derived from tt(Base),
which itself is instantiated for tt(First). This is acceptable, as the
compiler already has determined that the type tt(First) exists. At this point
that is all it needs.

    The second requirement is simply satisfied by defining the member
tt(polymorphic). In chapter ref(POLYMORPHISM) we saw that virtual and
overriding members belong to the class's private interface. We can apply the
same philosophy here, by placing tt(polymorphic) in tt(First's) private
interface, allowing it to be accessed from the base class by declaring
        verb(    friend void Base<First>::interface();)

tt(First's) complete class interface can now be designed, followed by
tt(polymorphic's) implementation:
        verb(    class First: public Base<First>
    {
        friend void Base<First>::interface();

        private:
            void polymorphic();
    };
    void First::polymorphic()
    {
        std::cout << "polymorphic from First\n";
    })

Note that the class tt(First) itself is not a class template: its members
can be separately compiled and stored in, e.g., a library. Also, as is the
case with dynamic polymorphism, the member tt(polymorphic) has full access to
all of tt(First)'s data members and member functions.

    Multiple classes can now be designed like tt(First), each offering their
own implementation of tt(polymorphic). E.g., the member
tt(Second::polymorphic) of the class tt(Second), designed like tt(First),
could be implemented like this:
        verb(    void Second::polymorphic()
    {
        std::cout << "polymorphic from Second\n";
    })

The polymorphic nature of tt(Base) becomes apparent once a function
template is defined in which tt(Base::interface) is called. Again, the
compiler simply assumes a member tt(interface) exists when it reads the
definition of the following function template:
        verb(    template <class Class>
    void fun(Class &object)
    {
        object.interface();
    })

Only where this function is actually called will the compiler verify the
viability of the generated code. In the following tt(main) function a
tt(First) object is passed to tt(fun): tt(First) declares tt(interface)
through its base class, and tt(First::polymorphic) is called by
tt(interface). The compiler will at this point (i.e., where tt(fun) is called)
check whether tt(first) indeed has a member tt(polymorphic). Next a tt(Second)
object is passed to tt(fun), and here again the compiler checks whether
tt(Second) has a member tt(Second::polymorphic):
        verb(    int main()
    {
        First first;
        fun(first);

        Second second;
        fun(second);
    })

There are also downsides to 
        hi(static polymorphism: downsides) 
    using static polymorphism:
        itemization(
        it() First, the sentence `a tt(Second) object is passed to tt(fun)'
formally isn't correct, since tt(fun) is a function template the functions
tt(fun) called as tt(fun(first)) and tt(fun(second)) are
em(different) functions, not just calls of one function with different
arguments. With static polymorphism every instantiation using its own template
parameters results in completely new code which is generated when the template
(e.g., tt(fun)) is instantiated. This is something to consider when creating
statically polymorphic base classes. If the base class defines data members
and member functions, and if these additional members are used by derived
class types, then each member has its own instantiation for each derived class
type. This also results in i(code bloat), albeit of a different kind than
observed with dynamic polymorphism. This kind of code bloat can often be
somewhat reduced by deriving the base class from its own (ordinary,
non-template) base class, encapsulating all elements of the statically
polymorphic base class that do not depend on its template type parameters.
        it() Second, if different types of statically polymorphic objects are
dynamically created (using the tt(new) operator) then the types of the
returned pointers are all different. In addition, the types of the pointers to
their statically polymorphic base classes differ from each other. These
latter pointers are different because they are pointers to tt(Base<Derived>),
representing different types for different tt(Derived) types. Consequently,
and different from dynamic polymorphism, these pointers cannot be collected
in, e.g., a vector of shared pointers to base class pointers. There simply
isn't one base class pointer type. Thus, because of the different base class
types, there's no direct statically polymorphic equivalent to virtual
destructors.
        it() Third, as illustrated in the next section, designing static
polymorphic classes using multiple levels of inheritance is not a trivial
task.
        )
    Summarizing, static polymorphism is best used in situations where a small
number of different derived class types are used, where a fixed number of
derived class objects are used, and where the statically polymorphic base
classes themselves are lean (possibly encapsulating some of their code in
ordinary base classes of their own).