| <?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"> |
| <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">Caveat</emphasis>: On most platforms, the |
| <libraryname>Type Traits</libraryname> templates |
| <code>has_nothrow_copy</code> and <code>has_nothrow_constructor</code> |
| by default return <code>false</code> for all <code>class</code> and |
| <code>struct</code> types. It is necessary therefore to provide |
| specializations of these templates as appropriate for user-defined |
| types, as demonstrated in the following: |
| |
| <programlisting>// ...in your code (at file scope)... |
| |
| namespace boost { |
| |
| template <> |
| struct <classname>has_nothrow_copy</classname>< myUDT > |
| : <classname>mpl::true_</classname> |
| { |
| }; |
| |
| } |
| </programlisting> |
| |
| </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> |