Boost C++ Libraries: Ticket #1910: function::swap should avoid doing memory allocations https://svn.boost.org/trac10/ticket/1910 <p> This ticket was triggered by a posting from Arvid Norberg, <a class="ext-link" href="http://lists.boost.org/Archives/boost/2008/02/133534.php"><span class="icon">​</span>boost::function&lt;&gt;::swap() can throw</a>. He was surprised to see that <code>boost::function::swap</code> can throw an exception, in case of a memory allocation failure. And he also remarked that TR1's <code>function::swap</code> is specified <em>not</em> to throw. </p> <p> The current <code>function::swap</code> (<a class="ext-link" href="http://svn.boost.org/trac/boost/browser/trunk/boost/function/function_template.hpp?rev=43884"><span class="icon">​</span>function_template.hpp, from trunk revision 43884</a>) is implemented by doing three copy operations: </p> <pre class="wiki"> void swap(BOOST_FUNCTION_FUNCTION&amp; other) { if (&amp;other == this) return; BOOST_FUNCTION_FUNCTION tmp = *this; *this = other; other = tmp; } </pre><p> I think that a major cause of exceptions can be taken away, by having <code>function::swap</code> implemented by means of <em>moving</em>, instead of copying: </p> <pre class="wiki"> void swap(BOOST_FUNCTION_FUNCTION&amp; other) { if (&amp;other == this) return; BOOST_FUNCTION_FUNCTION tmp; tmp.move_assign(*this); this-&gt;move_assign(other); other.move_assign(tmp); } </pre><p> This new <code>boost::function::move_assign</code> member function would in most cases just copy the function object held by its argument to <code>*this</code>, just like <code>boost::function</code>'s copy assignment operator does. But if the argument would hold its function object within a buffer <em>allocated on the heap</em>, <code>move_assign</code> would pass the buffer to <code>*this</code>, and set the argument's buffer pointer to <code>NULL</code>. </p> <p> The patch has <code>boost::function::move_assign</code> added to <code>function_template.hpp</code>. Out of modesty, I declared <code>move_assign</code> as a <em>private</em> member function, but feel free to make it public. Moreover, it could be changed to move <em>from</em> <code>*this</code> <em>to</em> its argument (and renamed, e.g., to <code>move_to</code>), so that it would support moving a temporary <em>(rvalue)</em>. But I think that's beyond the scope of this ticket... </p> <h2 class="section" id="Implementationdetails">Implementation details</h2> <p> The attached patch has a boolean argument added to all <code>manage</code> functions, telling whether or not the buffer should be moved. Thereby, the implementation of <code>move_assign</code> is quite similar to the existing <code>assign_to_own</code>, having the same call to <code>f.vtable-&gt;manager</code>, but passing <code>true</code> as last argument. </p> <p> This boolean argument <code>move_buffer</code> is only relevant to the specific <code>manager</code> overload for function objects that require heap allocation, which does in the patched version of <code>function_base.hpp</code>: </p> <pre class="wiki"> if (move_buffer) { out_buffer.obj_ptr = in_buffer.obj_ptr; in_buffer.obj_ptr = 0; } </pre><p> I made <code>obj_ptr</code> <em>mutable</em>, to work around the fact that <code>in_buffer</code> is a <em>const</em> reference. Instead, I guess a <code>const_cast</code> could be used, but I didn't have the guts to do so. ;-) </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/1910 Trac 1.4.3 niels_dekker Sat, 10 May 2008 13:02:36 GMT attachment set https://svn.boost.org/trac10/ticket/1910 https://svn.boost.org/trac10/ticket/1910 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">moving_function_swap.patch</span> </li> </ul> Ticket niels_dekker Fri, 29 Aug 2008 18:18:30 GMT cc set https://svn.boost.org/trac10/ticket/1910#comment:1 https://svn.boost.org/trac10/ticket/1910#comment:1 <ul> <li><strong>cc</strong> <span class="trac-author">nd_mail_address_valid_until_2008-12-31@…</span> added </li> </ul> Ticket Douglas Gregor Fri, 05 Sep 2008 15:41:45 GMT <link>https://svn.boost.org/trac10/ticket/1910#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/1910#comment:2</guid> <description> <p> This patch should give a significant performance improvement for swap(). Note, however, that it does not provide the nothrow guarantee for swap() due to the small-object optimization: small objects are still copied by move_assign, so their constructors could throw. We may or may not want a no-throw swap for Boost.Function, regardless of what std::(tr1::)function say; that we can handle on-list. </p> <p> I'm applying a variant of this patch to trunk now, and will close the ticket then. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Douglas Gregor</dc:creator> <pubDate>Fri, 05 Sep 2008 15:43:24 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/1910#comment:3 https://svn.boost.org/trac10/ticket/1910#comment:3 <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> (In <a class="changeset" href="https://svn.boost.org/trac10/changeset/48615" title="Improve the performance of Boost.Function's swap. Thanks to Niels ...">[48615]</a>) Improve the performance of Boost.Function's swap. Thanks to Niels Dekker for the original patch. Fixes <a class="closed ticket" href="https://svn.boost.org/trac10/ticket/1910" title="#1910: Bugs: function::swap should avoid doing memory allocations (closed: fixed)">#1910</a> </p> Ticket niels_dekker Fri, 05 Sep 2008 16:36:32 GMT <link>https://svn.boost.org/trac10/ticket/1910#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/1910#comment:4</guid> <description> <p> Thank you, Doug! I'm not sure if we should sacrifice the small-object optimization to get a <em>strictly</em> nothrow swap. For the time being, the function::swap performance improvement you've just committed looks like a reasonable compromise to me. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>niels_dekker</dc:creator> <pubDate>Fri, 05 Sep 2008 23:23:58 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/1910#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/1910#comment:5</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/1910#comment:3" title="Comment 3">dgregor</a>: </p> <blockquote class="citation"> <p> (In <a class="changeset" href="https://svn.boost.org/trac10/changeset/48615" title="Improve the performance of Boost.Function's swap. Thanks to Niels ...">[48615]</a>) Improve the performance of Boost.Function's swap. </p> </blockquote> <p> Sorry, I'm not yet entirely sure about your fix of <a class="ext-link" href="http://svn.boost.org/trac/boost/changeset/48615/trunk/boost/function/function_base.hpp"><span class="icon">​</span>function_base.hpp</a>, as it says in <em>manage_small</em>: </p> <pre class="wiki"> if (op == move_functor_tag) { reinterpret_cast&lt;functor_type*&gt;(&amp;in_buffer.data)-&gt;~Functor(); } </pre><p> As a consequence, it seems to me that the functor may be destructed too many times. The following BOOST_CHECK fails on my computer: </p> <pre class="wiki"> static unsigned construction_count; static unsigned destruction_count; struct MyFunctor { MyFunctor() { ++construction_count; } MyFunctor(const MyFunctor &amp;) { ++construction_count; } ~MyFunctor() { ++destruction_count; } int operator()() { return 0; } }; int test_main(int, char* []) { { boost::function0&lt;int&gt; f; boost::function0&lt;int&gt; g; f = MyFunctor(); g = MyFunctor(); f.swap(g); } // MyFunctor objects should be constructed at least // as many times as they are destructed. BOOST_CHECK(construction_count &gt;= destruction_count); } </pre> </description> <category>Ticket</category> </item> <item> <dc:creator>Douglas Gregor</dc:creator> <pubDate>Sat, 06 Sep 2008 03:16:26 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/1910#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/1910#comment:6</guid> <description> <p> (In <a class="changeset" href="https://svn.boost.org/trac10/changeset/48627" title="Fix double-destruction problem with small function objects and swap(), ...">[48627]</a>) Fix double-destruction problem with small function objects and swap(), and try to work around a GCC 4.2 issue. See <a class="closed ticket" href="https://svn.boost.org/trac10/ticket/1910" title="#1910: Bugs: function::swap should avoid doing memory allocations (closed: fixed)">#1910</a> for comments about the former problem from Niels Dekker. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>niels_dekker</dc:creator> <pubDate>Sun, 07 Sep 2008 21:20:55 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/1910#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/1910#comment:7</guid> <description> <p> Now that boost::function internally has a <em>move_assign</em> function, I think that the performance of its assignment operators can also be improved significantly, by calling <em>move_assign</em>, instead of <em>swap</em>. Please have a look at the attached function_assignment.patch </p> </description> <category>Ticket</category> </item> <item> <dc:creator>niels_dekker</dc:creator> <pubDate>Thu, 11 Sep 2008 15:40:01 GMT</pubDate> <title>attachment set https://svn.boost.org/trac10/ticket/1910 https://svn.boost.org/trac10/ticket/1910 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">function_assignment.patch</span> </li> </ul> <p> boost::function assignment operators calling move_assign. (Update: the patch now has a by-value argument for the copy assignment operator.) </p> Ticket niels_dekker Thu, 11 Sep 2008 16:16:41 GMT <link>https://svn.boost.org/trac10/ticket/1910#comment:8 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/1910#comment:8</guid> <description> <p> Please note: I made a separate ticket regarding my previous comment (including function_assignment.patch): Ticket <a class="new ticket" href="https://svn.boost.org/trac10/ticket/2319" title="#2319: Bugs: function::operator= should &#34;move&#34;, copy assignment should have ... (new)">#2319</a> - <em>function::operator= should "move", copy assignment should have by-value argument</em> </p> </description> <category>Ticket</category> </item> </channel> </rss>