File: classeshavingpointers.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 (121 lines) | stat: -rw-r--r-- 4,372 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
Classes having hi(class: having pointers) pointer data members require special
attention. In particular at construction time one must be careful to prevent
 hi(pointer: wild) wild pointers and/or hi(memory leak) memory leaks. Consider
the following class defining two pointer data members:
        verb(    class Filter
    {
        istream *d_in;
        ostream *d_out;
        public:
            Filter(char const *in, char const *out);
    };)

Assume that tt(Filter) objects filter information read from tt(*d_in) and
write the filtered information to tt(*d_out).  Using pointers to streams
allows us to have them point at any kind of stream like tt(istreams,
ifstreams, fstreams) or tt(istringstreams).  The shown constructor could be
implemented like this:
        verb(    Filter::Filter(char const *in, char const *out)
    :
        d_in(new ifstream{ in }),
        d_out(new ofstream{ out })
    {
        if (!*d_in || !*d_out)
            throw "Input and/or output stream not available"s;
    })

Of course, the construction could fail. tt(new) could throw an exception;
the stream constructors could throw exceptions; or the streams could not be
opened in which case an exception is thrown from the constructor's body. Using
a function try block helps. Note that if tt(d_in)'s initialization throws,
there's nothing to be worried about. The tt(Filter) object hasn't been
constructed, its destructor is not called and processing continues at the
point where the thrown exception is caught. But tt(Filter)'s destructor is
also not called when tt(d_out)'s initialization or the constructor's tt(if)
statement throws: no object, and hence no destructor is called. This may
result in memory leaks, as tt(delete) isn't called for tt(d_in) and/or
tt(d_out). To prevent this, tt(d_in) and tt(d_out) must first be initialized
to 0 and only then the initialization can be performed:
        verb(    Filter::Filter(char const *in, char const *out)
    try
    :
        d_in(0),
        d_out(0)
    {
        d_in = new ifstream{ in };
        d_out = new ofstream{ out };

        if (!*d_in || !*d_out)
            throw "Input and/or output stream not available"s;
    }
    catch (...)
    {
        delete d_out;
        delete d_in;
    })

This quickly gets complicated, though. If tt(Filter) harbors yet another
data member of a class whose constructor needs two streams then that data
cannot be constructed or it must itself be converted into a pointer:
        verb(    Filter::Filter(char const *in, char const *out)
    try
    :
        d_in(0),
        d_out(0)
        d_filterImp(*d_in, *d_out)    // won't work
    { ... }

    // instead:

    Filter::Filter(char const *in, char const *out)
    try
    :
        d_in(0),
        d_out(0),
        d_filterImp(0)
    {
        d_in = new ifstream(in);
        d_out = new ofstream(out);
        d_filterImp = new FilterImp(*d_in, *d_out);
        ...
    }
    catch (...)
    {
        delete d_filterImp;
        delete d_out;
        delete d_in;
    })

Although the latter alternative works, it quickly gets hairy. In
situations like these smart pointers should be used to prevent the
hairiness. By defining the stream pointers as (smart pointer) objects they
will, once constructed, properly be destroyed even if the rest of the
constructor's code throws exceptions. Using a tt(FilterImp) and two
tt(unique_ptr) data members tt(Filter)'s setup and its constructor becomes:
        verb(    class Filter
    {
        std::unique_ptr<std::ifstream> d_in;
        std::unique_ptr<std::ofstream> d_out;
        FilterImp d_filterImp;
        ...
    };

    Filter::Filter(char const *in, char const *out)
    try
    :
        d_in(new ifstream(in)),
        d_out(new ofstream(out)),
        d_filterImp(*d_in, *d_out)
    {
        if (!*d_in || !*d_out)
            throw "Input and/or output stream not available"s;
    })

We're back at the original implementation but this time without having to
worry about wild pointers and memory leaks. If one of the member initializers
throws the destructors of previously constructed data members (which are now
objects) are always called.

    As a i(rule of thumb): when classes need to define pointer data members
they should define those pointer data members as smart pointers if there's any
chance that their constructors throw exceptions.