311 lines
16 KiB
XML
311 lines
16 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN"
|
|
"http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
|
|
<!--
|
|
Copyright 2003, Eric Friedman, Itay Maman.
|
|
|
|
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
-->
|
|
<section id="variant.design">
|
|
<title>Design Overview</title>
|
|
|
|
<using-namespace name="boost"/>
|
|
|
|
<section id="variant.design.never-empty">
|
|
<title>"Never-Empty" Guarantee</title>
|
|
|
|
<section id="variant.design.never-empty.guarantee">
|
|
<title>The Guarantee</title>
|
|
|
|
<para>All instances <code>v</code> of type
|
|
<code><classname>variant</classname><T1,T2,...,TN></code>
|
|
guarantee that <code>v</code> has constructed content of one of the
|
|
types <code>T<emphasis>i</emphasis></code>, even if an operation on
|
|
<code>v</code> has previously failed.</para>
|
|
|
|
<para>This implies that <code>variant</code> may be viewed precisely as
|
|
a union of <emphasis>exactly</emphasis> its bounded types. This
|
|
"never-empty" property insulates the user from the
|
|
possibility of undefined <code>variant</code> content and the
|
|
significant additional complexity-of-use attendant with such a
|
|
possibility.</para>
|
|
</section>
|
|
|
|
<section id="variant.design.never-empty.problem">
|
|
<title>The Implementation Problem</title>
|
|
|
|
<para>While the
|
|
<link linkend="variant.design.never-empty.guarantee">never-empty guarantee</link>
|
|
might at first seem "obvious," it is in fact not even
|
|
straightforward how to implement it in general (i.e., without
|
|
unreasonably restrictive additional requirements on
|
|
<link linkend="variant.concepts.bounded-type">bounded types</link>).</para>
|
|
|
|
<para>The central difficulty emerges in the details of
|
|
<code>variant</code> assignment. Given two instances <code>v1</code>
|
|
and <code>v2</code> of some concrete <code>variant</code> type, there
|
|
are two distinct, fundamental cases we must consider for the assignment
|
|
<code>v1 = v2</code>.</para>
|
|
|
|
<para>First consider the case that <code>v1</code> and <code>v2</code>
|
|
each contains a value of the same type. Call this type <code>T</code>.
|
|
In this situation, assignment is perfectly straightforward: use
|
|
<code>T::operator=</code>.</para>
|
|
|
|
<para>However, we must also consider the case that <code>v1</code> and
|
|
<code>v2</code> contain values <emphasis>of distinct types</emphasis>.
|
|
Call these types <code>T</code> and <code>U</code>. At this point,
|
|
since <code>variant</code> manages its content on the stack, the
|
|
left-hand side of the assignment (i.e., <code>v1</code>) must destroy
|
|
its content so as to permit in-place copy-construction of the content
|
|
of the right-hand side (i.e., <code>v2</code>). In the end, whereas
|
|
<code>v1</code> began with content of type <code>T</code>, it ends
|
|
with content of type <code>U</code>, namely a copy of the content of
|
|
<code>v2</code>.</para>
|
|
|
|
<para>The crux of the problem, then, is this: in the event that
|
|
copy-construction of the content of <code>v2</code> fails, how can
|
|
<code>v1</code> maintain its "never-empty" guarantee?
|
|
By the time copy-construction from <code>v2</code> is attempted,
|
|
<code>v1</code> has already destroyed its content!</para>
|
|
</section>
|
|
|
|
<section id="variant.design.never-empty.memcpy-solution">
|
|
<title>The "Ideal" Solution: False Hopes</title>
|
|
|
|
<para>Upon learning of this dilemma, clever individuals may propose the
|
|
following scheme hoping to solve the problem:
|
|
|
|
<orderedlist>
|
|
<listitem>Provide some "backup" storage, appropriately
|
|
aligned, capable of holding values of the contained type of the
|
|
left-hand side.</listitem>
|
|
<listitem>Copy the memory (e.g., using <code>memcpy</code>) of the
|
|
storage of the left-hand side to the backup storage.</listitem>
|
|
<listitem>Attempt a copy of the right-hand side content to the
|
|
(now-replicated) left-hand side storage.</listitem>
|
|
<listitem>In the event of an exception from the copy, restore the
|
|
backup (i.e., copy the memory from the backup storage back into
|
|
the left-hand side storage).</listitem>
|
|
<listitem>Otherwise, in the event of success, now copy the memory
|
|
of the left-hand side storage to another "temporary"
|
|
aligned storage.</listitem>
|
|
<listitem>Now restore the backup (i.e., again copying the memory)
|
|
to the left-hand side storage; with the "old" content
|
|
now restored, invoke the destructor of the contained type on the
|
|
storage of the left-hand side.</listitem>
|
|
<listitem>Finally, copy the memory of the temporary storage to the
|
|
(now-empty) storage of the left-hand side.</listitem>
|
|
</orderedlist>
|
|
</para>
|
|
|
|
<para>While complicated, it appears such a scheme could provide the
|
|
desired safety in a relatively efficient manner. In fact, several
|
|
early iterations of the library implemented this very approach.</para>
|
|
|
|
<para>Unfortunately, as Dave Abraham's first noted, the scheme results
|
|
in undefined behavior:
|
|
|
|
<blockquote>
|
|
<para>"That's a lot of code to read through, but if it's
|
|
doing what I think it's doing, it's undefined behavior.</para>
|
|
<para>"Is the trick to move the bits for an existing object
|
|
into a buffer so we can tentatively construct a new object in
|
|
that memory, and later move the old bits back temporarily to
|
|
destroy the old object?</para>
|
|
<para>"The standard does not give license to do that: only one
|
|
object may have a given address at a time. See 3.8, and
|
|
particularly paragraph 4."</para>
|
|
</blockquote>
|
|
</para>
|
|
|
|
<para>Additionally, as close examination quickly reveals, the scheme has
|
|
the potential to create irreconcilable race-conditions in concurrent
|
|
environments.</para>
|
|
|
|
<para>Ultimately, even if the above scheme could be made to work on
|
|
certain platforms with particular compilers, it is still necessary to
|
|
find a portable solution.</para>
|
|
</section>
|
|
|
|
<section id="variant.design.never-empty.double-storage-solution">
|
|
<title>An Initial Solution: Double Storage</title>
|
|
|
|
<para>Upon learning of the infeasibility of the above scheme, Anthony
|
|
Williams proposed in
|
|
<link linkend="variant.refs.wil02">[Wil02]</link> a scheme that served
|
|
as the basis for a portable solution in some pre-release
|
|
implementations of <code>variant</code>.</para>
|
|
|
|
<para>The essential idea to this scheme, which shall be referred to as
|
|
the "double storage" scheme, is to provide enough space
|
|
within a <code>variant</code> to hold two separate values of any of
|
|
the bounded types.</para>
|
|
|
|
<para>With the secondary storage, a copy the right-hand side can be
|
|
attempted without first destroying the content of the left-hand side;
|
|
accordingly, the content of the left-hand side remains available in
|
|
the event of an exception.</para>
|
|
|
|
<para>Thus, with this scheme, the <code>variant</code> implementation
|
|
needs only to keep track of which storage contains the content -- and
|
|
dispatch any visitation requests, queries, etc. accordingly.</para>
|
|
|
|
<para>The most obvious flaw to this approach is the space overhead
|
|
incurred. Though some optimizations could be applied in special cases
|
|
to eliminate the need for double storage -- for certain bounded types
|
|
or in some cases entirely (see
|
|
<xref linkend="variant.design.never-empty.optimizations"/> for more
|
|
details) -- many users on the Boost mailing list strongly objected to
|
|
the use of double storage. In particular, it was noted that the
|
|
overhead of double storage would be at play at all times -- even if
|
|
assignment to <code>variant</code> never occurred. For this reason
|
|
and others, a new approach was developed.</para>
|
|
</section>
|
|
|
|
<section id="variant.design.never-empty.heap-backup-solution">
|
|
<title>Current Approach: Temporary Heap Backup</title>
|
|
|
|
<para>Despite the many objections to the double storage solution, it was
|
|
realized that no replacement would be without drawbacks. Thus, a
|
|
compromise was desired.</para>
|
|
|
|
<para>To this end, Dave Abrahams suggested to include the following in
|
|
the behavior specification for <code>variant</code> assignment:
|
|
"<code>variant</code> assignment from one type to another may
|
|
incur dynamic allocation." That is, while <code>variant</code> would
|
|
continue to store its content <emphasis>in situ</emphasis> after
|
|
construction and after assignment involving identical contained types,
|
|
<code>variant</code> would store its content on the heap after
|
|
assignment involving distinct contained types.</para>
|
|
|
|
<para>The algorithm for assignment would proceed as follows:
|
|
|
|
<orderedlist>
|
|
<listitem>Copy-construct the content of the right-hand side to the
|
|
heap; call the pointer to this data <code>p</code>.</listitem>
|
|
<listitem>Destroy the content of the left-hand side.</listitem>
|
|
<listitem>Copy <code>p</code> to the left-hand side
|
|
storage.</listitem>
|
|
</orderedlist>
|
|
|
|
Since all operations on pointers are nothrow, this scheme would allow
|
|
<code>variant</code> to meet its never-empty guarantee.
|
|
</para>
|
|
|
|
<para>The most obvious concern with this approach is that while it
|
|
certainly eliminates the space overhead of double storage, it
|
|
introduces the overhead of dynamic-allocation to <code>variant</code>
|
|
assignment -- not just in terms of the initial allocation but also
|
|
as a result of the continued storage of the content on the heap. While
|
|
the former problem is unavoidable, the latter problem may be avoided
|
|
with the following "temporary heap backup" technique:
|
|
|
|
<orderedlist>
|
|
<listitem>Copy-construct the content of the
|
|
<emphasis>left</emphasis>-hand side to the heap; call the pointer to
|
|
this data <code>backup</code>.</listitem>
|
|
<listitem>Destroy the content of the left-hand side.</listitem>
|
|
<listitem>Copy-construct the content of the right-hand side in the
|
|
(now-empty) storage of the left-hand side.</listitem>
|
|
<listitem>In the event of failure, copy <code>backup</code> to the
|
|
left-hand side storage.</listitem>
|
|
<listitem>In the event of success, deallocate the data pointed to
|
|
by <code>backup</code>.</listitem>
|
|
</orderedlist>
|
|
</para>
|
|
|
|
<para>With this technique: 1) only a single storage is used;
|
|
2) allocation is on the heap in the long-term only if the assignment
|
|
fails; and 3) after any <emphasis>successful</emphasis> assignment,
|
|
storage within the <code>variant</code> is guaranteed. For the
|
|
purposes of the initial release of the library, these characteristics
|
|
were deemed a satisfactory compromise solution.</para>
|
|
|
|
<para>There remain notable shortcomings, however. In particular, there
|
|
may be some users for which heap allocation must be avoided at all
|
|
costs; for other users, any allocation may need to occur via a
|
|
user-supplied allocator. These issues will be addressed in the future
|
|
(see <xref linkend="variant.design.never-empty.roadmap"/>). For now,
|
|
though, the library treats storage of its content as an implementation
|
|
detail. Nonetheless, as described in the next section, there
|
|
<emphasis>are</emphasis> certain things the user can do to ensure the
|
|
greatest efficiency for <code>variant</code> instances (see
|
|
<xref linkend="variant.design.never-empty.optimizations"/> for
|
|
details).</para>
|
|
</section>
|
|
|
|
<section id="variant.design.never-empty.optimizations">
|
|
<title>Enabling Optimizations</title>
|
|
|
|
<para>As described in
|
|
<xref linkend="variant.design.never-empty.problem"/>, the central
|
|
difficulty in implementing the never-empty guarantee is the
|
|
possibility of failed copy-construction during <code>variant</code>
|
|
assignment. Yet types with nothrow copy constructors clearly never
|
|
face this possibility. Similarly, if one of the bounded types of the
|
|
<code>variant</code> is nothrow default-constructible, then such a
|
|
type could be used as a safe "fallback" type in the event of
|
|
failed copy construction.</para>
|
|
|
|
<para>Accordingly, <code>variant</code> is designed to enable the
|
|
following optimizations once the following criteria on its bounded
|
|
types are met:
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>For each bounded type <code>T</code> that is nothrow
|
|
copy-constructible (as indicated by
|
|
<code><classname>boost::has_nothrow_copy</classname></code>), the
|
|
library guarantees <code>variant</code> will use only single
|
|
storage and in-place construction for <code>T</code>.</listitem>
|
|
|
|
<listitem>If <emphasis>any</emphasis> bounded type is nothrow
|
|
default-constructible (as indicated by
|
|
<code><classname>boost::has_nothrow_constructor</classname></code>),
|
|
the library guarantees <code>variant</code> will use only single
|
|
storage and in-place construction for <emphasis>every</emphasis>
|
|
bounded type in the <code>variant</code>. Note, however, that in
|
|
the event of assignment failure, an unspecified nothrow
|
|
default-constructible bounded type will be default-constructed in
|
|
the left-hand side operand so as to preserve the never-empty
|
|
guarantee.</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
</para>
|
|
|
|
<para><emphasis role="bold">Implementation Note</emphasis>: So as to make
|
|
the behavior of <code>variant</code> more predictable in the aftermath
|
|
of an exception, the current implementation prefers to default-construct
|
|
<code><classname>boost::blank</classname></code> if specified as a
|
|
bounded type instead of other nothrow default-constructible bounded
|
|
types. (If this is deemed to be a useful feature, it will become part
|
|
of the specification for <code>variant</code>; otherwise, it may be
|
|
obsoleted. Please provide feedback to the Boost mailing list.)</para>
|
|
</section>
|
|
|
|
<section id="variant.design.never-empty.roadmap">
|
|
<title>Future Direction: Policy-based Implementation</title>
|
|
|
|
<para>As the previous sections have demonstrated, much effort has been
|
|
expended in an attempt to provide a balance between performance, data
|
|
size, and heap usage. Further, significant optimizations may be
|
|
enabled in <code>variant</code> on the basis of certain traits of its
|
|
bounded types.</para>
|
|
|
|
<para>However, there will be some users for whom the chosen compromise
|
|
is unsatisfactory (e.g.: heap allocation must be avoided at all costs;
|
|
if heap allocation is used, custom allocators must be used; etc.). For
|
|
this reason, a future version of the library will support a
|
|
policy-based implementation of <code>variant</code>. While this will
|
|
not eliminate the problems described in the previous sections, it will
|
|
allow the decisions regarding tradeoffs to be decided by the user
|
|
rather than the library designers.</para>
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</section>
|