From 885ccc7ff368fa36cec0066503b272c2f44d7d40 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 27 Mar 2016 19:52:36 -0400 Subject: [PATCH 1/7] Added support for forwarding references in push() to allow for move construction and emplacement --- include/boost/lockfree/spsc_queue.hpp | 33 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index 5ecfb2a..e64a5de 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -98,7 +98,8 @@ class ringbuffer_base return write_available(write_index, read_index, max_size); } - bool push(T const & t, T * buffer, size_t max_size) + template + bool push(T * buffer, size_t max_size, Args&&... args) { const size_t write_index = write_index_.load(memory_order_relaxed); // only written from push thread const size_t next = next_index(write_index, max_size); @@ -106,7 +107,7 @@ class ringbuffer_base if (next == read_index_.load(memory_order_acquire)) return false; /* ringbuffer is full */ - new (buffer + write_index) T(t); // copy-construct + new (buffer + write_index) T(std::forward(args)...); // construct write_index_.store(next, memory_order_release); @@ -445,9 +446,11 @@ class compile_time_sized_ringbuffer: } public: - bool push(T const & t) + + template + bool push(Args&&... args) { - return ringbuffer_base::push(t, data(), max_size); + return ringbuffer_base::push( data(), max_size, std::forward(args)...); } template @@ -558,9 +561,10 @@ class runtime_sized_ringbuffer: Alloc::deallocate(array_, max_elements_); } - bool push(T const & t) + template + bool push(Args&&... args) { - return ringbuffer_base::push(t, &*array_, max_elements_); + return ringbuffer_base::push( &*array_, max_elements_, std::forward(args)...); } template @@ -601,28 +605,28 @@ class runtime_sized_ringbuffer: template ConstIterator push(ConstIterator begin, ConstIterator end) { - return ringbuffer_base::push(begin, end, &*array_, max_elements_); + return ringbuffer_base::push(begin, end, array_, max_elements_); } size_type pop(T * ret, size_type size) { - return ringbuffer_base::pop(ret, size, &*array_, max_elements_); + return ringbuffer_base::pop(ret, size, array_, max_elements_); } template size_type pop_to_output_iterator(OutputIterator it) { - return ringbuffer_base::pop_to_output_iterator(it, &*array_, max_elements_); + return ringbuffer_base::pop_to_output_iterator(it, array_, max_elements_); } const T& front(void) const { - return ringbuffer_base::front(&*array_); + return ringbuffer_base::front(array_); } T& front(void) { - return ringbuffer_base::front(&*array_); + return ringbuffer_base::front(array_); } }; @@ -756,9 +760,12 @@ class spsc_queue: * * \note Thread-safe and wait-free * */ - bool push(T const & t) + + template + typename std::enable_if< std::is_constructible::value, bool>::type + push(Args&&... args) { - return base_type::push(t); + return base_type::push(std::forward(args)...); } /** Pops one object from ringbuffer. From 5aa79139f4e6df5e9d339b82916bc5282854eb19 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 27 Mar 2016 20:02:48 -0400 Subject: [PATCH 2/7] Added support for forwarding references in push() to allow for move construction and emplacement --- include/boost/lockfree/spsc_queue.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index e64a5de..bf970cc 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -605,28 +605,28 @@ class runtime_sized_ringbuffer: template ConstIterator push(ConstIterator begin, ConstIterator end) { - return ringbuffer_base::push(begin, end, array_, max_elements_); + return ringbuffer_base::push(begin, end, &*array_, max_elements_); } size_type pop(T * ret, size_type size) { - return ringbuffer_base::pop(ret, size, array_, max_elements_); + return ringbuffer_base::pop(ret, size, &*array_, max_elements_); } template size_type pop_to_output_iterator(OutputIterator it) { - return ringbuffer_base::pop_to_output_iterator(it, array_, max_elements_); + return ringbuffer_base::pop_to_output_iterator(it, &*array_, max_elements_); } const T& front(void) const { - return ringbuffer_base::front(array_); + return ringbuffer_base::front(&*array_); } T& front(void) { - return ringbuffer_base::front(array_); + return ringbuffer_base::front(&*array_); } }; From 77dfc81c50faaa9d41b874bacd66b78beb2c79f9 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 27 Mar 2016 20:16:26 -0400 Subject: [PATCH 3/7] changed std:: specific constructs to boost:: --- include/boost/lockfree/spsc_queue.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index bf970cc..80b8924 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -762,7 +763,7 @@ class spsc_queue: * */ template - typename std::enable_if< std::is_constructible::value, bool>::type + typename boost::enable_if< typename boost::is_constructible::type, bool>::type push(Args&&... args) { return base_type::push(std::forward(args)...); From 62d20849c1b22b39205e089c099f67e9bb1b92a9 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sat, 9 Apr 2016 19:26:43 -0400 Subject: [PATCH 4/7] Added BOOST_HAS_RVALUE_REFS to check for r-value reference compliance --- include/boost/lockfree/spsc_queue.hpp | 117 +++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index 80b8924..ad507ef 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -18,7 +18,7 @@ #include #include #include -#include // for BOOST_LIKELY +#include // for BOOST_LIKELY and BOOST_HAS_RVALUE_REFS #include #include @@ -99,8 +99,7 @@ class ringbuffer_base return write_available(write_index, read_index, max_size); } - template - bool push(T * buffer, size_t max_size, Args&&... args) + bool push(T const & t, T * buffer, size_t max_size) { const size_t write_index = write_index_.load(memory_order_relaxed); // only written from push thread const size_t next = next_index(write_index, max_size); @@ -108,12 +107,48 @@ class ringbuffer_base if (next == read_index_.load(memory_order_acquire)) return false; /* ringbuffer is full */ - new (buffer + write_index) T(std::forward(args)...); // construct + new (buffer + write_index) T(t); // copy-construct write_index_.store(next, memory_order_release); return true; } +#ifdef BOOST_HAS_RVALUE_REFS + + bool push(T&& t, T * buffer, size_t max_size) + { + const size_t write_index = write_index_.load(memory_order_relaxed); // only written from push thread + const size_t next = next_index(write_index, max_size); + + if (next == read_index_.load(memory_order_acquire)) + return false; /* ringbuffer is full */ + + new (buffer + write_index) T(std::move(t)); // move-construct + + write_index_.store(next, memory_order_release); + + return true; + } + + + template + typename boost::enable_if< typename boost::is_constructible::type, bool>::type + emplace(T * buffer, size_t max_size, Args&&... args ) + { + const size_t write_index = write_index_.load(memory_order_relaxed); // only written from push thread + const size_t next = next_index(write_index, max_size); + + if (next == read_index_.load(memory_order_acquire)) + return false; /* ringbuffer is full */ + + new (buffer + write_index) T(std::forward(args)...); // emplace + + write_index_.store(next, memory_order_release); + + return true; + } + +#endif size_t push(const T * input_buffer, size_t input_count, T * internal_buffer, size_t max_size) { @@ -447,13 +482,27 @@ class compile_time_sized_ringbuffer: } public: + bool push(T const & t) + { + return ringbuffer_base::push(t, data(), max_size); + } + +#ifdef BOOST_HAS_RVALUE_REFS + bool push(T&& t) + { + return ringbuffer_base::push(std::move(t), data(), max_size); + } + template - bool push(Args&&... args) + typename boost::enable_if< typename boost::is_constructible::type, bool>::type + emplace(Args&&... args) { - return ringbuffer_base::push( data(), max_size, std::forward(args)...); + return ringbuffer_base::emplace(data(), max_size, std::forward(args)...); } +#endif + template bool consume_one(Functor & f) { @@ -562,12 +611,28 @@ class runtime_sized_ringbuffer: Alloc::deallocate(array_, max_elements_); } + bool push(T const & t) + { + return ringbuffer_base::push(t, &*array_, max_elements_); + } +#ifdef BOOST_HAS_RVALUE_REFS + + bool push(T&& t) + { + return ringbuffer_base::push(std::move(t), &*array_, max_elements_); + } + + + template - bool push(Args&&... args) + typename boost::enable_if< typename boost::is_constructible::type, bool>::type + emplace(Args&&... args) { - return ringbuffer_base::push( &*array_, max_elements_, std::forward(args)...); + return ringbuffer_base::emplace(&*array_, max_elements_, std::forward(args)...); } +#endif + template bool consume_one(Functor & f) { @@ -761,14 +826,46 @@ class spsc_queue: * * \note Thread-safe and wait-free * */ + bool push(T const & t) + { + return base_type::push(t); + } + + +#ifdef BOOST_HAS_RVALUE_REFS + + /** Pushes object t to the ringbuffer via move construction/ + * + * \pre only one thread is allowed to push data to the spsc_queue + * \post object will be pushed to the spsc_queue, unless it is full. + * \return true, if the push operation is successful. + * + * \note Thread-safe and wait-free + * */ + + bool push(T&& t) + { + return base_type::push(std::move(t)); + } + + /** Emplaces an instance of T to the ringbuffer via direct initialization using the given constructor arguments + * + * \pre only one thread is allowed to push data to the spsc_queue + * \post object will be pushed to the spsc_queue, unless it is full. + * \return true, if the push operation is successful. + * + * \note Thread-safe and wait-free + * */ template typename boost::enable_if< typename boost::is_constructible::type, bool>::type - push(Args&&... args) + emplace(Args&&... args) { - return base_type::push(std::forward(args)...); + return base_type::emplace(std::forward(args)...); } +#endif + /** Pops one object from ringbuffer. * * \pre only one thread is allowed to pop data to the spsc_queue From 9d59fbcf95b0ac882dafcaa361d8671bcf7d8b52 Mon Sep 17 00:00:00 2001 From: Josh Kilmer Date: Mon, 27 Feb 2017 16:36:34 -0600 Subject: [PATCH 5/7] spsc_queue: Use BOOST_NO_CXX11_RVALUE_REFERENCES instead of BOOST_HAS_RVALUE_REFS, add BOOST_NO_CXX11_VARIADIC_TEMPLATES guard around emplace --- include/boost/lockfree/spsc_queue.hpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index ad507ef..06a6af0 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -18,7 +18,7 @@ #include #include #include -#include // for BOOST_LIKELY and BOOST_HAS_RVALUE_REFS +#include // for BOOST_LIKELY, BOOST_NO_CXX11_RVALUE_REFERENCES, and BOOST_NO_CXX11_VARIADIC_TEMPLATES #include #include @@ -113,7 +113,7 @@ class ringbuffer_base return true; } -#ifdef BOOST_HAS_RVALUE_REFS +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES bool push(T&& t, T * buffer, size_t max_size) { @@ -130,7 +130,7 @@ class ringbuffer_base return true; } - +#ifndef BOOST_NO_CXX11_VARIADIC_TEMPLATES template typename boost::enable_if< typename boost::is_constructible::type, bool>::type emplace(T * buffer, size_t max_size, Args&&... args ) @@ -141,12 +141,13 @@ class ringbuffer_base if (next == read_index_.load(memory_order_acquire)) return false; /* ringbuffer is full */ - new (buffer + write_index) T(std::forward(args)...); // emplace + new (buffer + write_index) T{std::forward(args)...}; // emplace write_index_.store(next, memory_order_release); return true; } +#endif #endif @@ -487,19 +488,20 @@ class compile_time_sized_ringbuffer: return ringbuffer_base::push(t, data(), max_size); } -#ifdef BOOST_HAS_RVALUE_REFS +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES bool push(T&& t) { return ringbuffer_base::push(std::move(t), data(), max_size); } - +#ifndef BOOST_NO_CXX11_VARIADIC_TEMPLATES template typename boost::enable_if< typename boost::is_constructible::type, bool>::type emplace(Args&&... args) { return ringbuffer_base::emplace(data(), max_size, std::forward(args)...); } +#endif #endif @@ -615,21 +617,21 @@ class runtime_sized_ringbuffer: { return ringbuffer_base::push(t, &*array_, max_elements_); } -#ifdef BOOST_HAS_RVALUE_REFS +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES bool push(T&& t) { return ringbuffer_base::push(std::move(t), &*array_, max_elements_); } - - +#ifndef BOOST_NO_CXX11_VARIADIC_TEMPLATES template typename boost::enable_if< typename boost::is_constructible::type, bool>::type emplace(Args&&... args) { return ringbuffer_base::emplace(&*array_, max_elements_, std::forward(args)...); } +#endif #endif @@ -831,8 +833,7 @@ class spsc_queue: return base_type::push(t); } - -#ifdef BOOST_HAS_RVALUE_REFS +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES /** Pushes object t to the ringbuffer via move construction/ * @@ -848,6 +849,7 @@ class spsc_queue: return base_type::push(std::move(t)); } +#ifndef BOOST_NO_CXX11_VARIADIC_TEMPLATES /** Emplaces an instance of T to the ringbuffer via direct initialization using the given constructor arguments * * \pre only one thread is allowed to push data to the spsc_queue @@ -856,13 +858,13 @@ class spsc_queue: * * \note Thread-safe and wait-free * */ - template typename boost::enable_if< typename boost::is_constructible::type, bool>::type emplace(Args&&... args) { return base_type::emplace(std::forward(args)...); } +#endif #endif From 94ceb42e0518525a794ba1002b4ac3b7bbeffbe5 Mon Sep 17 00:00:00 2001 From: Josh Kilmer Date: Mon, 27 Feb 2017 20:22:26 -0600 Subject: [PATCH 6/7] spsc_queue: Move where it's possible, copy when it isn't --- include/boost/lockfree/spsc_queue.hpp | 37 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index 06a6af0..ea84359 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -123,7 +124,7 @@ class ringbuffer_base if (next == read_index_.load(memory_order_acquire)) return false; /* ringbuffer is full */ - new (buffer + write_index) T(std::move(t)); // move-construct + new (buffer + write_index) T(std::move_if_noexcept(t)); // move-construct write_index_.store(next, memory_order_release); @@ -317,12 +318,12 @@ class ringbuffer_base const size_t count0 = max_size - read_index; const size_t count1 = output_count - count0; - copy_and_delete(internal_buffer + read_index, internal_buffer + max_size, output_buffer); - copy_and_delete(internal_buffer, internal_buffer + count1, output_buffer + count0); + move_and_delete(internal_buffer + read_index, internal_buffer + max_size, output_buffer); + move_and_delete(internal_buffer, internal_buffer + count1, output_buffer + count0); new_read_index -= max_size; } else { - copy_and_delete(internal_buffer + read_index, internal_buffer + read_index + output_count, output_buffer); + move_and_delete(internal_buffer + read_index, internal_buffer + read_index + output_count, output_buffer); if (new_read_index == max_size) new_read_index = 0; } @@ -348,12 +349,12 @@ class ringbuffer_base const size_t count0 = max_size - read_index; const size_t count1 = avail - count0; - it = copy_and_delete(internal_buffer + read_index, internal_buffer + max_size, it); - copy_and_delete(internal_buffer, internal_buffer + count1, it); + it = move_and_delete(internal_buffer + read_index, internal_buffer + max_size, it); + move_and_delete(internal_buffer, internal_buffer + count1, it); new_read_index -= max_size; } else { - copy_and_delete(internal_buffer + read_index, internal_buffer + read_index + avail, it); + move_and_delete(internal_buffer + read_index, internal_buffer + read_index + avail, it); if (new_read_index == max_size) new_read_index = 0; } @@ -420,8 +421,28 @@ class ringbuffer_base return write_index == read_index; } +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES template< class OutputIterator > - OutputIterator copy_and_delete( T * first, T * last, OutputIterator out ) + typename boost::enable_if< typename boost::is_constructible::type, OutputIterator>::type + move_and_delete( T * first, T * last, OutputIterator out ) + { + if (boost::has_trivial_destructor::value) { + return std::move(first, last, out); // will use memcpy if possible + } else { + for (; first != last; ++first, ++out) { + *out = std::move(*first); + first->~T(); + } + return out; + } + } +#endif + + template< class OutputIterator > +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + typename boost::disable_if< typename boost::is_constructible::type, OutputIterator>::type +#endif + move_and_delete( T * first, T * last, OutputIterator out ) { if (boost::has_trivial_destructor::value) { return std::copy(first, last, out); // will use memcpy if possible From 8589b4f4a7f22ac61799e6f29c2feff74dc26d34 Mon Sep 17 00:00:00 2001 From: Josh Kilmer Date: Thu, 2 Mar 2017 04:18:00 -0600 Subject: [PATCH 7/7] spsc_queue: Add tests to verify compatibility with move-only and copy-only types Also: * Fix an issue spsc_queue::reset() would not compile for move-only types * Fix an issue where having BOOST_NO_CXX11_RVALUE_REFERENCES defined would break compiles --- include/boost/lockfree/spsc_queue.hpp | 5 +- test/spsc_queue_test.cpp | 159 ++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 2 deletions(-) diff --git a/include/boost/lockfree/spsc_queue.hpp b/include/boost/lockfree/spsc_queue.hpp index ea84359..449fed4 100644 --- a/include/boost/lockfree/spsc_queue.hpp +++ b/include/boost/lockfree/spsc_queue.hpp @@ -441,6 +441,8 @@ class ringbuffer_base template< class OutputIterator > #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES typename boost::disable_if< typename boost::is_constructible::type, OutputIterator>::type +#else + OutputIterator #endif move_and_delete( T * first, T * last, OutputIterator out ) { @@ -1092,8 +1094,7 @@ class spsc_queue: if ( !boost::has_trivial_destructor::value ) { // make sure to call all destructors! - T dummy_element; - while (pop(dummy_element)) + while (pop()) {} } else { base_type::write_index_.store(0, memory_order_relaxed); diff --git a/test/spsc_queue_test.cpp b/test/spsc_queue_test.cpp index 99f393f..88d7a23 100644 --- a/test/spsc_queue_test.cpp +++ b/test/spsc_queue_test.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "test_helpers.hpp" #include "test_common.hpp" @@ -405,3 +406,161 @@ BOOST_AUTO_TEST_CASE( spsc_queue_reset_test ) BOOST_REQUIRE(f.empty()); } + +struct immovable_type +{ + int value; + + immovable_type() : value(0) {} + immovable_type(int x) : value(x) {} + immovable_type(const immovable_type& other) : value(other.value) {} + + immovable_type& operator=(const immovable_type& other) + { + this->value = other.value; + return *this; + } + +#ifndef BOOST_NO_CXX11_DELETED_FUNCTIONS +#ifndef BOOST_NO_CX11_RVALUE_REFERENCES + immovable_type(immovable_type&&) = delete; + immovable_type& operator=(immovable_type&&) = delete; +#endif +#endif +}; + +struct test_immovable_object +{ + test_immovable_object(int i): + i(i) + {} + + void operator()(immovable_type arg) const + { + BOOST_REQUIRE_EQUAL(arg.value, i); + } + + int i; +}; + +BOOST_AUTO_TEST_CASE( spsc_queue_immovable_object_test ) +{ + spsc_queue > f; + + f.emplace( 9001 ); + f.emplace( 9002 ); + f.push( immovable_type(9003) ); + + immovable_type x(9004); + immovable_type push_array[2]; + push_array[0] = x; + push_array[1] = x; + f.push(push_array, 2); + + immovable_type pop_array[2]; + f.pop(pop_array, 2); + BOOST_CHECK_EQUAL( pop_array[0].value, 9001 ); + BOOST_CHECK_EQUAL( pop_array[1].value, 9002 ); + + f.consume_one( test_immovable_object(9003) ); + f.consume_all( test_immovable_object(9004) ); + + f.emplace( 42 ); + f.emplace( 42 ); + f.reset(); +} + +#ifndef BOOST_NO_CX11_RVALUE_REFERENCES +struct refcount_handle +{ + int* p_refcount; + + refcount_handle() : p_refcount( nullptr ) {} + refcount_handle(int* x) : p_refcount(x) + { + if (p_refcount != nullptr) + { + *p_refcount += 1; + } + } + ~refcount_handle() + { + if (p_refcount != nullptr) + { + *p_refcount -= 1; + } + } + + refcount_handle(const refcount_handle& other) = delete; + refcount_handle& operator=(const refcount_handle&) = delete; + + refcount_handle& operator=(refcount_handle&& other) + { + if (this->p_refcount != nullptr) + { + *p_refcount -= 1; + } + + this->p_refcount = other.p_refcount; + other.p_refcount = nullptr; + return *this; + } + refcount_handle(refcount_handle&& other) + : p_refcount( other.p_refcount ) + { + other.p_refcount = nullptr; + } +}; + +struct test_refcount_handle +{ + test_refcount_handle(int* p_refcount): + p_refcount(p_refcount) + {} + + void operator()(refcount_handle& arg) const + { + BOOST_REQUIRE_EQUAL(arg.p_refcount, p_refcount); + } + + int* p_refcount; +}; + +BOOST_AUTO_TEST_CASE( spsc_queue_refcount_test ) +{ + spsc_queue > f; + + int refcount = 0; + + f.emplace( &refcount ); + f.emplace( &refcount ); + f.emplace( &refcount ); + f.emplace( &refcount ); + BOOST_CHECK_EQUAL( refcount, 4 ); + + f.push( std::move( refcount_handle(&refcount) ) ); + + BOOST_CHECK_EQUAL( refcount, 5 ); + + { + refcount_handle pop_array[2]; + f.pop(pop_array, 2); + BOOST_CHECK_EQUAL( pop_array[0].p_refcount, &refcount ); + BOOST_CHECK_EQUAL( pop_array[1].p_refcount, &refcount ); + + BOOST_CHECK_EQUAL( refcount, 5 ); + } + BOOST_CHECK_EQUAL( refcount, 3 ); + + f.consume_one( test_refcount_handle(&refcount) ); + BOOST_CHECK_EQUAL( refcount, 2 ); + f.consume_all( test_refcount_handle(&refcount) ); + BOOST_CHECK_EQUAL( refcount, 0 ); + + f.emplace( &refcount ); + f.emplace( &refcount ); + BOOST_CHECK_EQUAL( refcount, 2 ); + f.reset(); + BOOST_CHECK_EQUAL( refcount, 0 ); +} +#endif