123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- <?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>
|