diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/irr_ptr.h | 137 | ||||
-rw-r--r-- | src/unittest/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/unittest/test_irrptr.cpp | 131 |
3 files changed, 269 insertions, 0 deletions
diff --git a/src/irr_ptr.h b/src/irr_ptr.h new file mode 100644 index 000000000..5022adb9d --- /dev/null +++ b/src/irr_ptr.h @@ -0,0 +1,137 @@ +/* +Minetest +Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once +#include <type_traits> +#include "irrlichttypes.h" +#include "IReferenceCounted.h" + +/** Shared pointer for IrrLicht objects. + * + * It should only be used for user-managed objects, i.e. those created with + * the @c new operator or @c create* functions, like: + * `irr_ptr<scene::IMeshBuffer> buf{new scene::SMeshBuffer()};` + * + * It should *never* be used for engine-managed objects, including + * those created with @c addTexture and similar methods. + */ +template <class ReferenceCounted, + class = typename std::enable_if<std::is_base_of<IReferenceCounted, + ReferenceCounted>::value>::type> +class irr_ptr +{ + ReferenceCounted *value = nullptr; + + /** Drops stored pointer replacing it with the given one. + * @note Copy semantics: reference counter *is* increased. + */ + void grab(ReferenceCounted *object) + { + if (object) + object->grab(); + reset(object); + } + +public: + irr_ptr() {} + + irr_ptr(std::nullptr_t) noexcept {} + + irr_ptr(const irr_ptr &b) noexcept { grab(b.get()); } + + irr_ptr(irr_ptr &&b) noexcept { reset(b.release()); } + + template <typename B, class = typename std::enable_if<std::is_convertible<B *, + ReferenceCounted *>::value>::type> + irr_ptr(const irr_ptr<B> &b) noexcept + { + grab(b.get()); + } + + template <typename B, class = typename std::enable_if<std::is_convertible<B *, + ReferenceCounted *>::value>::type> + irr_ptr(irr_ptr<B> &&b) noexcept + { + reset(b.release()); + } + + /** Constructs a shared pointer out of a plain one + * @note Move semantics: reference counter is *not* increased. + */ + explicit irr_ptr(ReferenceCounted *object) noexcept { reset(object); } + + ~irr_ptr() { reset(); } + + irr_ptr &operator=(const irr_ptr &b) noexcept + { + grab(b.get()); + return *this; + } + + irr_ptr &operator=(irr_ptr &&b) noexcept + { + reset(b.release()); + return *this; + } + + template <typename B, class = typename std::enable_if<std::is_convertible<B *, + ReferenceCounted *>::value>::type> + irr_ptr &operator=(const irr_ptr<B> &b) noexcept + { + grab(b.get()); + return *this; + } + + template <typename B, class = typename std::enable_if<std::is_convertible<B *, + ReferenceCounted *>::value>::type> + irr_ptr &operator=(irr_ptr<B> &&b) noexcept + { + reset(b.release()); + return *this; + } + + ReferenceCounted &operator*() const noexcept { return *value; } + ReferenceCounted *operator->() const noexcept { return value; } + explicit operator ReferenceCounted *() const noexcept { return value; } + explicit operator bool() const noexcept { return !!value; } + + /** Returns the stored pointer. + */ + ReferenceCounted *get() const noexcept { return value; } + + /** Returns the stored pointer, erasing it from this class. + * @note Move semantics: reference counter is not changed. + */ + ReferenceCounted *release() noexcept + { + ReferenceCounted *object = value; + value = nullptr; + return object; + } + + /** Drops stored pointer replacing it with the given one. + * @note Move semantics: reference counter is *not* increased. + */ + void reset(ReferenceCounted *object = nullptr) noexcept + { + if (value) + value->drop(); + value = object; + } +}; diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 71aa1fa56..82f9a4a13 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -10,6 +10,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp new file mode 100644 index 000000000..aa857ff46 --- /dev/null +++ b/src/unittest/test_irrptr.cpp @@ -0,0 +1,131 @@ +/* +Minetest +Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "exceptions.h" +#include "irr_ptr.h" + +class TestIrrPtr : public TestBase +{ +public: + TestIrrPtr() { TestManager::registerTestModule(this); } + const char *getName() { return "TestIrrPtr"; } + + void runTests(IGameDef *gamedef); + + void testRefCounting(); + void testSelfAssignment(); + void testNullHandling(); +}; + +static TestIrrPtr g_test_instance; + +void TestIrrPtr::runTests(IGameDef *gamedef) +{ + TEST(testRefCounting); + TEST(testSelfAssignment); + TEST(testNullHandling); +} + +//////////////////////////////////////////////////////////////////////////////// + +#define UASSERT_REFERENCE_COUNT(object, value, info) \ + UTEST((object)->getReferenceCount() == value, \ + info "Reference count is %d instead of " #value, \ + (object)->getReferenceCount()) + +void TestIrrPtr::testRefCounting() +{ + IReferenceCounted *obj = new IReferenceCounted(); // RC=1 + obj->grab(); + UASSERT_REFERENCE_COUNT(obj, 2, "Pre-condition failed: "); + { + irr_ptr<IReferenceCounted> p1{obj}; // move semantics + UASSERT(p1.get() == obj); + UASSERT_REFERENCE_COUNT(obj, 2, ); + + irr_ptr<IReferenceCounted> p2{p1}; // copy ctor + UASSERT(p1.get() == obj); + UASSERT(p2.get() == obj); + UASSERT_REFERENCE_COUNT(obj, 3, ); + + irr_ptr<IReferenceCounted> p3{std::move(p1)}; // move ctor + UASSERT(p1.get() == nullptr); + UASSERT(p3.get() == obj); + UASSERT_REFERENCE_COUNT(obj, 3, ); + + p1 = std::move(p2); // move assignment + UASSERT(p1.get() == obj); + UASSERT(p2.get() == nullptr); + UASSERT_REFERENCE_COUNT(obj, 3, ); + + p2 = p3; // copy assignment + UASSERT(p2.get() == obj); + UASSERT(p3.get() == obj); + UASSERT_REFERENCE_COUNT(obj, 4, ); + + p1.release(); + UASSERT(p1.get() == nullptr); + UASSERT_REFERENCE_COUNT(obj, 4, ); + } + UASSERT_REFERENCE_COUNT(obj, 2, ); + obj->drop(); + UTEST(obj->drop(), "Dropping failed: reference count is %d", + obj->getReferenceCount()); +} + +void TestIrrPtr::testSelfAssignment() +{ + irr_ptr<IReferenceCounted> p1{new IReferenceCounted()}; + UASSERT(p1); + UASSERT_REFERENCE_COUNT(p1, 1, ); + p1 = p1; + UASSERT(p1); + UASSERT_REFERENCE_COUNT(p1, 1, ); + p1 = std::move(p1); + UASSERT(p1); + UASSERT_REFERENCE_COUNT(p1, 1, ); +} + +void TestIrrPtr::testNullHandling() +{ + // In the case of an error, it will probably crash with SEGV. + // Nevertheless, UASSERTs are used to catch possible corner cases. + irr_ptr<IReferenceCounted> p1{new IReferenceCounted()}; + UASSERT(p1); + irr_ptr<IReferenceCounted> p2; + UASSERT(!p2); + irr_ptr<IReferenceCounted> p3{p2}; + UASSERT(!p2); + UASSERT(!p3); + irr_ptr<IReferenceCounted> p4{std::move(p2)}; + UASSERT(!p2); + UASSERT(!p4); + p2 = p2; + UASSERT(!p2); + p2 = std::move(p2); + UASSERT(!p2); + p3 = p2; + UASSERT(!p2); + UASSERT(!p3); + p3 = std::move(p2); + UASSERT(!p2); + UASSERT(!p3); +} |