File: assignment.yo

package info (click to toggle)
c%2B%2B-annotations 10.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 10,536 kB
  • ctags: 3,247
  • sloc: cpp: 19,157; makefile: 1,521; ansic: 165; sh: 128; perl: 90
file content (156 lines) | stat: -rw-r--r-- 6,185 bytes parent folder | download | duplicates (3)
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
To assign a tt(Data) object to another data object, we need an assignment
operator. The standard mold for the assignment operator looks like this:
        verb(
    Class &Class::operator=(Class const &other)
    {
        Class tmp(other);
        swap(*this, tmp);
        return *this;
    }
        )
    This implementation is exception safe: it offers the `commit or roll-back'
guarantee (cf. section ref(CopyDestroy)). But can it be applied to tt(Data)?

It depends. It depends on whether tt(Data) objects can be em(fast swapped)
(cf. section ref(FSWAP)) or not. If tt(Union)'s fields can be fast swapped
then we can simply swap bytes and we're done. In that case tt(Union) does not
require any additional members (to be specific: it won't need an assignment
operator).

But now assume that tt(Union)'s fields cannot be fast swapped. How to
implement an exception-safe assignment (i.e., an assignment offering the
`commit or roll-back' guarantee) in that case? The tt(d_tag) field clearly
isn't a problem, so we delegate the responsibility for proper assignment to
tt(Union), implementing tt(Data)'s assignment operators as follows:
        verb(
    Data &Data::operator=(Data const &rhs)
    {
        if (d_union.assign(d_tag, rhs.d_union, rhs.d_tag))
            d_tag = rhs.d_tag;
        return *this;
    }

    Data &Data::operator=(Data &&tmp)
    {
        if (d_union.assign(d_tag, std::move(tmp.d_union), tmp.d_tag))
            d_tag = tmp.d_tag;
        return *this;
    }
        )

But now for tt(Union::assign). Assuming that both tt(Unions) use different
fields, but swapping objects of the separate types is allowed.  Now things
may go wrong. Assume the left-side union uses type X, the right-side
union uses type Y and both types use allocation. First, briefly look at
standard swapping. It involves three steps:
    itemization(
    it() tt(tmp(lhs)): initialize a temporary objecct;
    it() tt(lhs = rhs): assign the rhs object to the lhs object;
    it() tt(rhs = tmp): assign the tmp object to the rhs
    )
    Usually we assume that these steps do not throw exceptions, as tt(swap)
itself shouldn't throw exceptions. How could we implement
swapping for our union? Assume the fields are known (easily done by passing
tt(Tag) values to tt(Union::swap)):
    itemization(
    it() tt(X tmp(lhs.x)): initialize a temporary X;
    it() in-place destroy lhs.x; placement new initialize lhs.y from rhs.y
        (alternatively: placement new default initialize lhs.y, then do the
        standard lhs.y = rhs.y)
    it() in-place destroy rhs.y; placement new initialize rhs.x from tmp
(alternatively: placement new default initialize rhs.x, then do the standard
rhs.x = tmp)
    )

By bf(C++)-standard requirement, the in-place destruction won't throw. Since
the standard swap also performs an assignment that part should work fine as
well. And since the standard swap also does a copy construction the placement
new operations should perform fine as well, and if so, tt(Union) may be
provided with the following tt(swap) member:
        verb(
    void Data::Union::swap(Tag myTag, Union &other, Tag oTag)
    {
        Union tmp(*this, myTag);    // save lhs

        destroy(myTag);             // destroy lhs
        copy(other, oTag);          // assign rhs

        other.destroy(oTag);        // destroy rhs
        other.copy(tmp, myTag);     // save lhs via tmp
    }
        )

Now that tt(swap) is available tt(Data)'s assignment operators are easily
realized:
        verb(
    Data &Data::operator=(Data const &rhs)
    {
        Data tmp(rhs);  // tmp(std::move(rhs)) for the move assignment

        d_union.swap(d_tag, tmp.d_union, tmp.d_tag);
        swap(d_tag, tmp.d_tag);

        return *this;
    }
        )

What if the tt(Union) constructors em(could) throw? In that case we can
provide tt(Data) with an 'commit or roll-back' assignment operator like this:
        verb(
    Data &Data::operator=(Data const &rhs)
    {
        Data tmp(rhs);
                            // rolls back before throwing an exception
        d_union.assign(d_tag, rhs.d_union, rhs.d_tag);
        d_tag = rhs.d_tag;

        return *this;
    }
        )

How to implement tt(Union::assign)? Here are the steps tt(assign) must take:
    itemization(
    it() First save the current union in a block of memory. This merely
involves a non-throwing tt(memcpy) operation;
    it() Then use placement new to copy the other object's union field into
the current object. If this throws:
        itemization(
        it() catch the exception, restore the original tt(Union) from the
saved block and rethrow the exception: we have rolled-back to our previous
(valid) state.
        )
    it() We still have to delete the original field's allocated data. To do
so, we perform the following steps:
        itemization(
        it() (Fast) swap the current union's new contents with the contents in
the previously saved block;
        it() Call tt(destroy) for the now restored original union;
        it() Re-install the new union from the memory block.
        )
        As none of the above steps will throw, we have committed the new
situation.
    )
    Here is the implementation of the `commit or roll-back' tt(Union::assign):
        verb(
    void Data::Union::assign(Tag myTag, Union const &other, Tag otag)
    {
        char saved[sizeof(Union)];
        memcpy(saved, this, sizeof(Union));     // raw copy: saved <- *this
        try
        {
            copy(other, otag);                  // *this = other: may throw
            fswap(*this,                        // *this <-> saved
                    *reinterpret_cast<Union *>(saved));
            destroy(myTag);                     // destroy original *this
            memcpy(this, saved, sizeof(Union)); // install new *this
        }
        catch (...)                             // copy threw
        {
            memcpy(this, saved, sizeof(Union)); // roll back: restore *this
            throw;
        }
    }
        )
    The source distribution contains
tt(yo/containers/examples/unrestricted2.cc) offering a small demo-program in
which the here developed tt(Data) class is used.