cppannotations/annotations/yo/containers/assignment.yo
Frank B. Brokken 777b182edd Moved all files but 'excluded', 'sf', and 'sourcetar' to ./annotations
This allowed me to standardize the sourcetar and sf/* scripts: the base
    directory (containing ./git) is now empty, except for maintenance scripts,
    while the source files and build scripts of the annotations are stored in
    a subdirectory of their own.
2013-05-29 20:44:08 +02:00

156 lines
6 KiB
Text

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.