Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>As announced in the question, edit 3:</p> <p>Here is another solution which, as far as I can judge, fulfills the requirements in the question. It is similar to the solution described in the original question, but uses <code>boost::shared_ptr</code> instead of a custom smart pointer.</p> <p>The central idea of this solution is to provide a <code>release()</code> operation on <code>shared_ptr</code>. If we can make the <code>shared_ptr</code> give up its ownership, we are free to call a cleanup function, delete the object, and throw an exception in case an error occurred during cleanup.</p> <p>Boost has a <a href="http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/shared_ptr.htm" rel="nofollow noreferrer">good reason</a> to not provide a <code>release()</code> operation on <code>shared_ptr</code>:</p> <blockquote> <p>shared_ptr cannot give away ownership unless it's unique() because the other copy will still destroy the object.</p> <p>Consider:</p> <pre><code>shared_ptr&lt;int&gt; a(new int); shared_ptr&lt;int&gt; b(a); // a.use_count() == b.use_count() == 2 int * p = a.release(); // Who owns p now? b will still call delete on it in its destructor. </code></pre> <p>Furthermore, the pointer returned by release() would be difficult to deallocate reliably, as the source shared_ptr could have been created with a custom deleter.</p> </blockquote> <p>The first argument against a <code>release()</code> operation is that, by the nature of <code>shared_ptr</code>, many pointers share ownership of the object, so no single one of them can simply release that ownership. But what if the <code>release()</code> function returned a null pointer if there were still other references left? The <code>shared_ptr</code> can reliably determine this, without race conditions.</p> <p>The second argument against the <code>release()</code> operation is that, if a custom deleter was passed to the <code>shared_ptr</code>, you should use that to deallocate the object, rather than simply deleting it. But <code>release()</code> could return a function object, in addition to the raw pointer, to enable its caller to deallocate the pointer reliably.</p> <p>However, in our specific szenario, custom deleters will not be an issue, because we do not have to deal with arbitrary custom deleters. This will become clearer from the code given below.</p> <p>Providing a <code>release()</code> operation on <code>shared_ptr</code> without modifying its implementation is, of course, not possible without a hack. The hack which is used in the code below relies on a thread-local variable to prevent our custom deleter from actually deleting the object.</p> <p>That said, here's the code, consisting mostly of the header <code>Resource.hpp</code>, plus a small implementation file <code>Resource.cpp</code>. Note that it must be linked with <code>-lboost_thread-mt</code> due to the thread-local variable.</p> <pre><code>// --------------------------------------------------------------------- // Resource.hpp // --------------------------------------------------------------------- #include &lt;boost/assert.hpp&gt; #include &lt;boost/ref.hpp&gt; #include &lt;boost/shared_ptr.hpp&gt; #include &lt;boost/thread/tss.hpp&gt; /// Factory for a resource. template&lt;typename T&gt; struct ResourceFactory { /// Create a resource. static boost::shared_ptr&lt;T&gt; create() { return boost::shared_ptr&lt;T&gt;(new T, ResourceFactory()); } template&lt;typename A1&gt; static boost::shared_ptr&lt;T&gt; create(const A1&amp; a1) { return boost::shared_ptr&lt;T&gt;(new T(a1), ResourceFactory()); } template&lt;typename A1, typename A2&gt; static boost::shared_ptr&lt;T&gt; create(const A1&amp; a1, const A2&amp; a2) { return boost::shared_ptr&lt;T&gt;(new T(a1, a2), ResourceFactory()); } // ... /// Destroy a resource. static void destroy(boost::shared_ptr&lt;T&gt;&amp; resource); /// Deleter for boost::shared_ptr&lt;T&gt;. void operator()(T* resource); }; namespace impl { // --------------------------------------------------------------------- /// Return the last reference to the resource, or zero. Resets the pointer. template&lt;typename T&gt; T* release(boost::shared_ptr&lt;T&gt;&amp; resource); /// Return true if the resource should be deleted (thread-local). bool wantDelete(); // --------------------------------------------------------------------- } // namespace impl template&lt;typename T&gt; inline void ResourceFactory&lt;T&gt;::destroy(boost::shared_ptr&lt;T&gt;&amp; ptr) { T* resource = impl::release(ptr); if (resource != 0) // Is it the last reference? { try { resource-&gt;close(); } catch (...) { delete resource; throw; } delete resource; } } // --------------------------------------------------------------------- template&lt;typename T&gt; inline void ResourceFactory&lt;T&gt;::operator()(T* resource) { if (impl::wantDelete()) { try { resource-&gt;close(); } catch (...) { } delete resource; } } namespace impl { // --------------------------------------------------------------------- /// Flag in thread-local storage. class Flag { public: ~Flag() { m_ptr.release(); } Flag&amp; operator=(bool value) { if (value != static_cast&lt;bool&gt;(*this)) { if (value) { m_ptr.reset(s_true); // may throw boost::thread_resource_error! } else { m_ptr.release(); } } return *this; } operator bool() { return m_ptr.get() == s_true; } private: boost::thread_specific_ptr&lt;char&gt; m_ptr; static char* s_true; }; // --------------------------------------------------------------------- /// Flag to prevent deletion. extern Flag t_nodelete; // --------------------------------------------------------------------- /// Return the last reference to the resource, or zero. template&lt;typename T&gt; T* release(boost::shared_ptr&lt;T&gt;&amp; resource) { try { BOOST_ASSERT(!t_nodelete); t_nodelete = true; // may throw boost::thread_resource_error! } catch (...) { t_nodelete = false; resource.reset(); throw; } T* rv = resource.get(); resource.reset(); return wantDelete() ? rv : 0; } // --------------------------------------------------------------------- } // namespace impl </code></pre> <p>And the implementation file:</p> <pre><code>// --------------------------------------------------------------------- // Resource.cpp // --------------------------------------------------------------------- #include "Resource.hpp" namespace impl { // --------------------------------------------------------------------- bool wantDelete() { bool rv = !t_nodelete; t_nodelete = false; return rv; } // --------------------------------------------------------------------- Flag t_nodelete; // --------------------------------------------------------------------- char* Flag::s_true((char*)0x1); // --------------------------------------------------------------------- } // namespace impl </code></pre> <p>And here is an example of a resource class implemented using this solution:</p> <pre><code>// --------------------------------------------------------------------- // example.cpp // --------------------------------------------------------------------- #include "Resource.hpp" #include &lt;cstdlib&gt; #include &lt;string&gt; #include &lt;stdexcept&gt; #include &lt;iostream&gt; // uncomment to test failed resource allocation, usage, and deallocation //#define TEST_CREAT_FAILURE //#define TEST_USAGE_FAILURE //#define TEST_CLOSE_FAILURE // --------------------------------------------------------------------- /// The low-level resource type. struct foo { char c; }; // --------------------------------------------------------------------- /// The low-level function to allocate the resource. foo* foo_open() { #ifdef TEST_CREAT_FAILURE return 0; #else return (foo*) std::malloc(sizeof(foo)); #endif } // --------------------------------------------------------------------- /// Some low-level function using the resource. int foo_use(foo*) { #ifdef TEST_USAGE_FAILURE return -1; #else return 0; #endif } // --------------------------------------------------------------------- /// The low-level function to free the resource. int foo_close(foo* foo) { std::free(foo); #ifdef TEST_CLOSE_FAILURE return -1; #else return 0; #endif } // --------------------------------------------------------------------- /// The C++ wrapper around the low-level resource. class Foo { public: void use() { if (foo_use(m_foo) &lt; 0) { throw std::runtime_error("foo_use"); } } protected: Foo() : m_foo(foo_open()) { if (m_foo == 0) { throw std::runtime_error("foo_open"); } } void close() { if (foo_close(m_foo) &lt; 0) { throw std::runtime_error("foo_close"); } } private: foo* m_foo; friend struct ResourceFactory&lt;Foo&gt;; }; // --------------------------------------------------------------------- typedef ResourceFactory&lt;Foo&gt; FooFactory; // --------------------------------------------------------------------- /// Main function. int main() { try { boost::shared_ptr&lt;Foo&gt; resource = FooFactory::create(); resource-&gt;use(); FooFactory::destroy(resource); } catch (const std::exception&amp; e) { std::cerr &lt;&lt; e.what() &lt;&lt; std::endl; } return 0; } </code></pre> <p>Finally, here is a small Makefile to build all that:</p> <pre><code># Makefile CXXFLAGS = -g -Wall example: example.cpp Resource.hpp Resource.o $(CXX) $(CXXFLAGS) -o example example.cpp Resource.o -lboost_thread-mt Resource.o: Resource.cpp Resource.hpp $(CXX) $(CXXFLAGS) -c Resource.cpp -o Resource.o clean: rm -f Resource.o example </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload