mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-16 07:48:44 +01:00
777b182edd
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.
156 lines
6 KiB
Text
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.
|