File: constructors.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 (156 lines) | stat: -rw-r--r-- 7,712 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
    Object destructors are only activated for completely constructed objects.
Although this may
sound like a truism, there is a subtlety here. If the construction of an
object fails for some reason, the object's destructor is em(not) called when
the object goes out of scope. This could happen if an exception that is
 hi(exception: and constructors)hi(constructor: and exceptions)
    generated by the constructor is not caught by the constructor. If the
exception is thrown when the object has already allocated some memory, then
that memory is not returned: its destructor isn't called as the object's
construction wasn't successfully completed.

    The following example illustrates this situation in its prototypical
form. The constructor of the class tt(Incomplete) first displays a message
and then throws an exception. Its destructor also displays a message:
        verbinclude(//INCOMPLETE examples/constructor.cc)
    Next, tt(main()) creates an tt(Incomplete) object inside a tt(try)
block. Any exception that may be generated is subsequently caught:
        verbinclude(//MAIN examples/constructor.cc)
    When this program is run, it produces the following output:
        verb(    Creating `Incomplete' object
    Allocated some memory
    Caught exception)

Thus, if tt(Incomplete)'s constructor would actually have allocated some
memory, the program would suffer from a memory leak. To prevent this from
happening, the following counter measures are available:
    itemization(
    it() Prevent the exceptions from leaving the constructor.nl()
        If part of the constructor's body may generate exceptions, then this
part may be surrounded by a tt(try) block, allowing the exception to be caught
by the constructor itself. This approach is defensible when the constructor
is able to repair the cause of the exception and to complete its construction
as a valid object.
    it() If an exception is generated by a base class constructor or by a
member initializing constructor then a tt(try) block within the constructor's
 body won't be able to catch the thrown exception. This em(always) results in
the exception leaving the constructor and the object is not considered to have
been properly constructed. A tt(try) block may include the member
initializers, and the tt(try) block's compound statement becomes the
constructor's body as in the following example:
    verb(class Incomplete2
{
    Composed d_composed;
    public:
        Incomplete2()
        try
        :
            d_composed(/* arguments */)
        {
            // body
        }
        catch (...)
        {}
};)

An exception thrown by either the member initializers or the body
results in the execution never reaching the body's closing curly brace. Instead
the catch clause is reached. Since the constructor's body isn't properly
completed the object is not considered properly constructed and eventually the
object's destructor won't be called.
    )

    The catch clause of a constructor's function tt(try) block behaves
slightly different than a catch clause of an ordinary function tt(try)
block. An exception reaching a constructor's function tt(try) block may be
transformed into another exception (which is thrown from the catch clause) but
if no exception is explicitly thrown from the catch clause the exception
originally reaching the catch clause is always rethrown. Consequently, there's
no way to confine an exception thrown from a base class constructor or from a
member initializer to the constructor: such an exception em(always) propagates
to a more shallow block and in that case the object's construction is always
considered incomplete.

    Therefore, if incompletely constructed objects throw exceptions then
the constructor em(remains responsible) for preventing memory
(generally: resource) leaks. There are several ways to realize this:
    itemization(
    it() If the constructor defines a function try block, then the
constructor's catch clause em(cannot) use em(any) of its data members
anymore. By implication: if the constructor has allocated memory pointed to by
one of its data members, then the catch-clause cannot delete that
memory. Instead, the constructor's body itself must ensure that the memory is
properly deleted;
    it() When multiple inheritance is used: if initial base classes have
properly been constructed and a later base class throws, then the initial base
class objects are automatically destroyed (as they are themselves fully
constructed objects)
    it() When composition is used: already constructed composed objects are
automatically destroyed (as they are fully constructed objects)
    it() Instead of using plain pointers em(smart pointers) (cf. section
ref(SHAREDPTR)) should be used to manage dynamically allocated memory. In this
case, if the constructor throws either before or after the allocation of the
dynamic memory, then allocated memory is properly returned as tt(shared_ptr)
objects are, after all, objects.
    it() If plain pointer data members em(must) be used then the constructor's
body should first, in its member initialization section, initialize its plain
pointer data members. Then, in its body it can dynamically allocate memory,
reassigning the plain pointer data members. In these cases the
pointer-handling must be embedded in a try-block, allowing the constructor to
free the allocated memory before a final exception is thrown, possibly 
reaching the catch clause of the constructor's function try block. That final
catch clause can complete whatever actions are required (e.g., write a
log-file entry). Example:
    verb(class Incomplete2
{
    Composed d_composed;
    char *d_cp;         // plain pointers
    int *d_ip;

    public:
        Incomplete2(size_t nChars, size_t nInts)
        try
        :
            d_composed(/* arguments */),    // might throw
            d_cp(0),
            d_ip(0)
        {
            try
            {
                preamble();                     // might throw
                d_cp = new char[nChars];        // might throw
                d_ip = new int[nChars];         // might throw
                postamble();                    // might throw
            }
            catch (...)
            {
                delete[] d_cp;                  // clean up
                delete[] d_ip;
                throw;                          // retrow the exception
            }
        }
        catch (...)
        {
            // maybe write a log-entry, but also throws 
            // the original exception
        }
};)

)

    On the other hand, bf(C++) supports constructor delegation, so an object
may have been completely constructed according to the bf(C++) run-time system,
but yet its (delegating) constructor may throw an exception, as illustrated by
the next example:
        verbinsert(-ans4 examples/delegate.cc)
    Here it is the responsibility of tt(Delegate)'s designer to ensure that
the throwing default constructor does not invalidate the actions performed by
the tt(Delegate(int x)) constructor. The latter constructor is called (line
12) by the default constructor, and merely initializes (lines 28, 29) the data
members at lines 6 and 7. Next, the default constructor, after allocating some
memory, throws an exception (line 16). In fact, an exception may be called at
any point, since the destructor (line 18) will be called automatically anyway
(line 41). If multiple exceptions could be thrown then tt(Delegate) can define
an enumeration and a data member of that enumeration type, which is set to the
enum value indication the nature of the next exception (if it is thrown), so
the destructor can handle the exception according to its type.