diff --git a/src/rcu.h b/src/rcu.h --- a/src/rcu.h +++ b/src/rcu.h @@ -77,9 +77,11 @@ template class RCUPtr { T *ptr; + // Private construction, so factories have to be used. + explicit RCUPtr(T *ptrIn) : ptr(ptrIn) {} + public: RCUPtr() : ptr(nullptr) {} - explicit RCUPtr(T *ptrIn) : ptr(ptrIn) {} ~RCUPtr() { if (ptr != nullptr) { @@ -87,6 +89,15 @@ } } + /** + * Acquire ownership of some pointer. + */ + static RCUPtr acquire(T *&ptrIn) { + RCUPtr ret(ptrIn); + ptrIn = nullptr; + return ret; + } + /** * Construct a new object that is owned by the pointer. */ @@ -94,6 +105,17 @@ return RCUPtr(new T(std::forward(args)...)); } + /** + * Construct a new RCUPtr without transfering owership. + */ + static RCUPtr copy(T *ptr) { + if (ptr != nullptr) { + ptr->acquire(); + } + + return RCUPtr::acquire(ptr); + } + /** * Copy semantic. */ @@ -119,9 +141,30 @@ } /** - * Accessors + * Get allows to access the undelying pointer. RCUPtr keeps ownership. + */ + T *get() { return ptr; } + const T *get() const { return ptr; } + + /** + * Release transfers ownership of the pointer from RCUPtr to the caller. + */ + T *release() { + T *oldPtr = ptr; + ptr = nullptr; + return oldPtr; + } + + /** + * Operator overloading for convenience. */ T *operator->() { return ptr; } + const T *operator->() const { return ptr; } + + T &operator*() { return *ptr; } + const T &operator*() const { return *ptr; } + + explicit operator bool() const { return ptr != nullptr; } }; #define IMPLEMENT_RCU_REFCOUNT(T) \ diff --git a/src/test/rcu_tests.cpp b/src/test/rcu_tests.cpp --- a/src/test/rcu_tests.cpp +++ b/src/test/rcu_tests.cpp @@ -134,6 +134,15 @@ } } +/** + * Version of Boost::test prior to 1.64 have issues when dealing with nullptr_t. + * In order to work around this, we ensure that the null pointers are typed in a + * way that Boost will like better. + * + * TODO: Use nullptr directly once the minimum version of boost is 1.64 or more. + */ +#define NULLPTR(T) static_cast(nullptr) + BOOST_AUTO_TEST_CASE(cleanup_test) { BOOST_CHECK(RCUTest::getCleanups().empty()); @@ -200,7 +209,11 @@ BOOST_AUTO_TEST_CASE(rcuref_test) { // Make sure it works for null. - { RCUPtr rcuptr(nullptr); } + { + RCURefTestItem *ptr = nullptr; + RCUPtr::copy(ptr); + RCUPtr::acquire(ptr); + } // Check the destruction mechanism. bool isDestroyed = false; @@ -241,6 +254,19 @@ BOOST_CHECK(!isDestroyed); RCULock::synchronize(); BOOST_CHECK(isDestroyed); + + // Check various operators. + BOOST_CHECK_EQUAL(gptr.get(), NULLPTR(RCURefTestItem)); + BOOST_CHECK_EQUAL(gptr.get(), &*gptr); + BOOST_CHECK(!gptr); + + auto ptr = new RCURefTestItem([] {}); + auto oldPtr = ptr; + gptr = RCUPtr::acquire(ptr); + + BOOST_CHECK_EQUAL(gptr.get(), oldPtr); + BOOST_CHECK_EQUAL(&*gptr, oldPtr); + BOOST_CHECK(gptr); } class RCURefMoveTestItem { @@ -312,6 +338,29 @@ BOOST_CHECK(!isDestroyed); RCULock::synchronize(); BOOST_CHECK(isDestroyed); + + // Acquire/release workflow. + isDestroyed = false; + auto ptr = new RCURefMoveTestItem([&] { isDestroyed = true; }); + auto ptrCopy = ptr; + BOOST_CHECK_THROW(RCUPtr::copy(ptr), + std::runtime_error); + + rcuptr1 = RCUPtr::acquire(ptr); + BOOST_CHECK_EQUAL(rcuptr1.get(), ptrCopy); + BOOST_CHECK_EQUAL(ptr, NULLPTR(RCURefMoveTestItem)); + + ptr = rcuptr1.release(); + BOOST_CHECK_EQUAL(rcuptr1.get(), NULLPTR(RCURefMoveTestItem)); + BOOST_CHECK_EQUAL(ptr, ptrCopy); + + RCULock::synchronize(); + BOOST_CHECK(!isDestroyed); + + RCUPtr::acquire(ptr); + BOOST_CHECK(!isDestroyed); + RCULock::synchronize(); + BOOST_CHECK(isDestroyed); } BOOST_AUTO_TEST_SUITE_END()