Boost C++ Libraries: Ticket #6575: copy_remaining_to and copy_some_and_update don't use allocator_traits for construction https://svn.boost.org/trac10/ticket/6575 <p> In advanced_insert_aux_emplace in advanced_insert_int.hpp, neither copy_remaining_to nor copy_some_and_update use allocator_traits for construction, even though the uninitialized versions do. This is problematic for allocators that need to alter the constructor's arguments list. </p> <p> Thanks, Erik Jensen </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/6575 Trac 1.4.3 Ion Gaztañaga Sat, 18 Feb 2012 08:41:35 GMT <link>https://svn.boost.org/trac10/ticket/6575#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6575#comment:1</guid> <description> <p> copy operations do not propagate the allocator because all the do is copy assignment, not copy construction. Allocators are propagated only in copy construction, as already built elements should be already constructed with the scoped allocator propagation model. That's consistent with the C++11 container implementations I've reviewed. </p> </description> <category>Ticket</category> </item> <item> <author>Erik Jensen <Erik.Jensen@…></author> <pubDate>Sun, 19 Feb 2012 02:57:30 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/6575#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6575#comment:2</guid> <description> <p> Certainly copy assignment should not propagate the allocator. The problem with these two functions is that they construct a new object on the stack which they then move/copy into place. It is this initial construction that is the issue, not the subsequent move/copy. </p> <p> The following is an attempt of mine to fix the issue (there may well be a better way, but my knowledge here is limited): </p> <pre class="wiki"> virtual void copy_remaining_to(Iterator p) \ { \ if(!this-&gt;used_){ \ aligned_storage&lt;sizeof(value_type), alignment_of&lt;value_type&gt;::value&gt; v; \ value_type *vp = static_cast&lt;value_type *&gt;(static_cast&lt;void *&gt;(&amp;v)); \ alloc_traits::construct(a_, vp \ BOOST_PP_ENUM_TRAILING(n, BOOST_CONTAINER_PP_MEMBER_FORWARD, _)); \ *p = boost::move(*vp); \ this-&gt;used_ = true; \ } \ } \ </pre><p> I believe this is important for a few reasons. First, there are probably other uses for the allocator wanting to handle object construction than just propagating the allocator. Second, if construct is being used for allocator propagation, the allocator probably doesn't have a default constructor, leading to a build error with the current implementation. Finally, even if the allocator does have a default constructor, having a different allocator would probably force a potentially expensive copy from the temporary rather that a cheap move operation, defeating an important reason to use emplace. </p> <p> Thanks,<br /> Erik Jensen </p> </description> <category>Ticket</category> </item> <item> <author>Erik Jensen <Erik.Jensen@…></author> <pubDate>Sun, 19 Feb 2012 03:09:07 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/6575#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6575#comment:3</guid> <description> <p> Err... forgot to destruct the temporary. It should have been this: </p> <pre class="wiki">virtual void copy_remaining_to(Iterator p) \ { \ if(!this-&gt;used_){ \ aligned_storage&lt;sizeof(value_type), alignment_of&lt;value_type&gt;::value&gt; v; \ value_type *vp = static_cast&lt;value_type *&gt;(static_cast&lt;void *&gt;(&amp;v)); \ alloc_traits::construct(a_, vp \ BOOST_PP_ENUM_TRAILING(n, BOOST_CONTAINER_PP_MEMBER_FORWARD, _)); \ try { \ *p = boost::move(*vp); \ } catch (...) { \ alloc_traits::destroy(a_, vp); \ throw; \ } \ alloc_traits::destroy(a_, vp); \ this-&gt;used_ = true; \ } \ } \ </pre> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Sun, 19 Feb 2012 15:59:38 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/6575#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6575#comment:4</guid> <description> <p> Both libc++ (clang) and libstdc++ (g++) implement the same approach as Boost.Container. The inserted element gets the container allocator as it is moved to an existing object built with with allocator_traits. I think the issue is not clear, and only happens in vector and deque (node-based containers allocates some memory and build the object directly there). </p> <p> Instead of aligned storage, or a temporary, which might use some undefined stack space (we don't know how big T is), another alternative is to reserve additional capacity for two new elements at the end of the vector/deque before emplacing (instead of just one, it's really a negligible cost, as the vector capacity grows exponentially). Then built the object with allocator_traits in the end() + 1 position (with a rollback operation to destroy the element at the end of the scope, as it should be destroyed anyway after being moved to the right position). </p> <p> This is not simple to do in Boost.Container as all [uninitialized]copy operations are hidden behind an abstract interface to avoid instantiating insertion bookeeing functions for each iterator type, but maybe we need some interface extension. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Mon, 20 Feb 2012 12:33:15 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/6575#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6575#comment:5</guid> <description> <p> I think you've found a LWG issue here. We might end up calling two different functions if we call emplace_back(usually implemented via allocator_traits at the end of the buffer) or emplace(iterator, ...) (usually implemented via a temporary moved to the required position). </p> <p> I think you're proposal is the correct one, construct the temporary via allocator_traits: the programmer does not receive any surprising error with non-default constructible allocators, performance is optimum and the constructor to be called is always well-known by the programmer. Thanks again for the report. </p> </description> <category>Ticket</category> </item> <item> <author>Erik Jensen <Erik.Jensen@…></author> <pubDate>Fri, 24 Feb 2012 03:27:49 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/6575#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6575#comment:6</guid> <description> <p> For what it's worth, the STL implementation that ships with the Visual Studio 11 preview solves this problem by always doing an emplace_back, and then calling std::rotate if necessary to get everything into the correct spot. This way, it is always calling construct on an uninitialized memory location. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Fri, 24 Aug 2012 21:43:07 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/6575#comment:7 https://svn.boost.org/trac10/ticket/6575#comment:7 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> Fixed in Boost 1.50 using an aligned buffer. </p> Ticket