File: destructors.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 (96 lines) | stat: -rw-r--r-- 4,922 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
    According to the bf(C++) standard exceptions thrown by destructors may
em(not) leave their bodies. Providing a destructor with a function tt(try)
block is therefore a violation of the standard: exceptions caught by a
function tt(try) block's catch clause have already left the destructor's body.
If --in violation of the standard-- the destructor em(is) provided with a
function tt(try) block and an exception is caught by the tt(try) block then
that exception is rethrown, similar to what happens in catch clauses of
constructor functions' tt(try) blocks.

The consequences of  an exception leaving the destructor's body is not
defined, and may result in unexpected behavior. Consider the following example:

Assume a carpenter builds a cupboard having a single drawer. The cupboard
is finished, and a customer, buying the cupboard, finds that the cupboard can
be used as expected. Satisfied with the cupboard, the customer asks the
carpenter to build another cupboard, this time having em(two)
drawers. When the second cupboard is finished, the customer takes it home and
is utterly amazed when the second cupboard completely collapses immediately
after it is used for the first time.

    Weird story? Then consider the following program:
        verbinclude(//MAIN examples/destructor.cc)
    When this program is run it produces the following output:
        verb(    Creating Cupboard1
    Drawer 1 used
    Cupboard1 behaves as expected
    Creating Cupboard2
    Drawer 2 used
    Drawer 1 used
    terminate called after throwing an instance of 'int'
    Abort)

The final tt(Abort) indicates that the program has aborted instead of
displaying a message like tt(Cupboard2 behaves as expected).

Let's have a look at the three classes involved. The class tt(Drawer) has no
particular characteristics, except that its destructor throws an exception:
        verbinclude(//DRAWER examples/destructor.cc)
    The class tt(Cupboard1) has no special characteristics at all. It merely
has a single composed tt(Drawer) object:
        verbinclude(//CUP1 examples/destructor.cc)
    The class tt(Cupboard2) is constructed comparably, but it has two
composed tt(Drawer) objects:
        verbinclude(//CUP2 examples/destructor.cc)

    When tt(Cupboard1)'s destructor is called tt(Drawer)'s destructor is
eventually called to destroy its composed object. This destructor throws an
exception, which is caught beyond the program's first tt(try) block. This
behavior is completely as expected.

A subtlety here is that tt(Cupboard1)'s destructor (and hence tt(Drawer)'s
destructor) is activated em(immediately) subsequent to its construction. Its
destructor is called immediately subsequent to its construction as
tt(Cupboard1()) defines an anonymous object. As a result the tt(Beyond
Cupboard1 object) text is never inserted into tt(std::cerr).

    Because of tt(Drawer)'s destructor throwing an exception a problem occurs
when tt(Cupboard2)'s destructor is called. Of its two composed objects, the
second tt(Drawer)'s destructor is called first.  This destructor throws an
exception, which ought to be caught beyond the program's second tt(try)
block. However, although the flow of control by then has left the context of
tt(Cupboard2)'s destructor, that object hasn't completely been destroyed yet
as the destructor of its other (left) tt(Drawer) still has to be called.

    Normally that would not be a big problem: once an exception is thrown from
tt(Cupboard2)'s destructor any remaining actions would simply be ignored,
albeit that (as both drawers are properly constructed objects) tt(left)'s
destructor would still have to be called.

    This happens here too and tt(left)'s destructor em(also) needs to throw an
exception. But as we've already left the context of the second tt(try) block,
the current flow control is now thoroughly mixed up, and the program has no
other option but to abort. It does so by calling tt(terminate()), which in
turn calls tt(abort()). Here we have our collapsing cupboard having two
drawers, even though the cupboard having one drawer behaves perfectly.

    The program aborts since there are multiple composed objects whose
destructors throw exceptions leaving the destructors. In this situation one of
the composed objects would throw an exception by the time the program's flow
control has already left its proper context causing the program to abort.

    The bf(C++) standard therefore understandably stipulates that exceptions
may em(never) leave destructors. Here is the skeleton of a destructor whose
 hi(destructor: and exceptions)hi(exception: and destructors) code might throw
exceptions. No function tt(try) block but all the destructor's actions are
encapsulated in a tt(try) block nested under the destructor's body.
        verb(    Class::~Class()
    {
        try
        {
            maybe_throw_exceptions();
        }
        catch (...)
        {}
    })