diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/meson.build | 1 | ||||
| -rw-r--r-- | src/stdplus/handle/copyable.hpp | 112 | ||||
| -rw-r--r-- | test/Makefile.am | 5 | ||||
| -rw-r--r-- | test/handle/copyable.cpp | 347 | ||||
| -rw-r--r-- | test/meson.build | 1 |
7 files changed, 469 insertions, 0 deletions
@@ -41,5 +41,6 @@ Makefile.in /src/stdplus.pc # Output binaries +/test/handle/copyable /test/handle/managed /test/signal diff --git a/src/Makefile.am b/src/Makefile.am index 57af82a..d45007e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,6 +4,8 @@ lib_LTLIBRARIES = libstdplus.la libstdplus_la_SOURCES = libstdplus_la_LIBADD = $(COMMON_LIBS) +nobase_include_HEADERS += stdplus/handle/copyable.hpp + nobase_include_HEADERS += stdplus/handle/managed.hpp nobase_include_HEADERS += stdplus/signal.hpp diff --git a/src/meson.build b/src/meson.build index 6b1fea9..a6ff714 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,5 +20,6 @@ install_headers( subdir: 'stdplus') install_headers( + 'stdplus/handle/copyable.hpp', 'stdplus/handle/managed.hpp', subdir: 'stdplus/handle') diff --git a/src/stdplus/handle/copyable.hpp b/src/stdplus/handle/copyable.hpp new file mode 100644 index 0000000..df383db --- /dev/null +++ b/src/stdplus/handle/copyable.hpp @@ -0,0 +1,112 @@ +#pragma once +#include <optional> +#include <stdplus/handle/managed.hpp> +#include <utility> + +namespace stdplus +{ + +/** @brief Similar to the Managed Handle, but also allows for copying + * and performs an operation during that copy. + */ +template <typename T, typename... As> +struct Copyable +{ + template <void (*drop)(T&&, As&...), T (*ref)(const T&, As&...)> + class Handle : public Managed<T, As...>::template Handle<drop> + { + public: + using MHandle = typename Managed<T, As...>::template Handle<drop>; + + /** @brief Creates a handle referencing the object + * + * @param[in] maybeV - Optional object being managed + */ + template <typename... Vs> + constexpr explicit Handle(const std::optional<T>& maybeV, Vs&&... vs) : + MHandle(std::nullopt, std::forward<Vs>(vs)...) + { + reset(maybeV); + } + template <typename... Vs> + constexpr explicit Handle(const T& maybeV, Vs&&... vs) : + MHandle(std::nullopt, std::forward<Vs>(vs)...) + { + reset(maybeV); + } + + /** @brief Creates a handle owning the object + * + * @param[in] maybeV - Maybe the object being managed + */ + template <typename... Vs> + constexpr explicit Handle(std::optional<T>&& maybeV, + Vs&&... vs) noexcept : + MHandle(std::move(maybeV), std::forward<Vs>(vs)...) + { + } + template <typename... Vs> + constexpr explicit Handle(T&& maybeV, Vs&&... vs) noexcept : + MHandle(std::move(maybeV), std::forward<Vs>(vs)...) + { + } + + constexpr Handle(const Handle& other) : MHandle(std::nullopt, other.as) + { + reset(other.maybe_value()); + } + + constexpr Handle(Handle&& other) noexcept : MHandle(std::move(other)) + { + } + + constexpr Handle& operator=(const Handle& other) + { + if (this != &other) + { + reset(); + this->as = other.as; + reset(other.maybe_value()); + } + return *this; + } + + constexpr Handle& operator=(Handle&& other) noexcept + { + MHandle::operator=(std::move(other)); + return *this; + } + + using MHandle::reset; + + /** @brief Resets the managed value to a new value + * Takes a new reference on the value + * + * @param[in] maybeV - Maybe the new value + */ + constexpr void reset(const std::optional<T>& maybeV) + { + if (maybeV) + { + reset(doRef(*maybeV, std::index_sequence_for<As...>())); + } + else + { + reset(std::nullopt); + } + } + constexpr void reset(const T& maybeV) + { + reset(doRef(maybeV, std::index_sequence_for<As...>())); + } + + private: + template <size_t... Indices> + T doRef(const T& v, std::index_sequence<Indices...>) + { + return ref(v, std::get<Indices>(this->as)...); + } + }; +}; + +} // namespace stdplus diff --git a/test/Makefile.am b/test/Makefile.am index 45f54f4..76cd56f 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -7,6 +7,11 @@ gtest_ldadd = $(STDPLUS_LIBS) $(GTEST_LIBS) $(GMOCK_LIBS) -lgmock_main check_PROGRAMS = TESTS = $(check_PROGRAMS) +check_PROGRAMS += handle/copyable +handle_copyable_SOURCES = handle/copyable.cpp +handle_copyable_CPPFLAGS = $(gtest_cppflags) +handle_copyable_LDADD = $(gtest_ldadd) + check_PROGRAMS += handle/managed handle_managed_SOURCES = handle/managed.cpp handle_managed_CPPFLAGS = $(gtest_cppflags) diff --git a/test/handle/copyable.cpp b/test/handle/copyable.cpp new file mode 100644 index 0000000..6ce210f --- /dev/null +++ b/test/handle/copyable.cpp @@ -0,0 +1,347 @@ +#include <gtest/gtest.h> +#include <optional> +#include <stdplus/handle/copyable.hpp> +#include <string> +#include <utility> +#include <vector> + +namespace stdplus +{ +namespace +{ + +static std::vector<int> reffed; +static int stored_ref = 0; +static std::vector<int> dropped; +static int stored_drop = 0; + +int ref(const int& i) +{ + reffed.push_back(i); + return i + 1; +} + +void drop(int&& i) +{ + dropped.push_back(std::move(i)); +} + +int ref(const int& i, std::string&, int& si) +{ + reffed.push_back(i); + // Make sure we can update the stored data + stored_ref = si++; + return i + 1; +} + +void drop(int&& i, std::string&, int& si) +{ + dropped.push_back(std::move(i)); + // Make sure we can update the stored data + stored_drop = si++; +} + +using SimpleHandle = Copyable<int>::Handle<drop, ref>; +using StoreHandle = Copyable<int, std::string, int>::Handle<drop, ref>; + +class CopyableHandleTest : public ::testing::Test +{ + protected: + void SetUp() + { + reffed.clear(); + dropped.clear(); + } + + void TearDown() + { + EXPECT_TRUE(reffed.empty()); + EXPECT_TRUE(dropped.empty()); + } +}; + +TEST_F(CopyableHandleTest, EmptyNoStorage) +{ + SimpleHandle h(std::nullopt); + EXPECT_FALSE(h); + EXPECT_THROW(h.value(), std::bad_optional_access); + h.reset(); + EXPECT_FALSE(h); + EXPECT_THROW(h.value(), std::bad_optional_access); +} + +TEST_F(CopyableHandleTest, EmptyWithStorage) +{ + auto maybeV = std::nullopt; + StoreHandle h(maybeV, "str", 5); + EXPECT_FALSE(h); + EXPECT_THROW(h.value(), std::bad_optional_access); + h.reset(maybeV); + EXPECT_FALSE(h); + EXPECT_THROW(h.value(), std::bad_optional_access); +} + +TEST_F(CopyableHandleTest, SimplePopulated) +{ + constexpr int expected = 3; + { + int val = expected; + SimpleHandle h(std::move(val)); + EXPECT_TRUE(h); + EXPECT_EQ(expected, *h); + EXPECT_EQ(expected, h.value()); + EXPECT_TRUE(dropped.empty()); + } + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, OptionalPopulated) +{ + constexpr int expected = 3; + { + std::optional<int> maybeVal{expected}; + SimpleHandle h(std::move(maybeVal)); + EXPECT_TRUE(h); + EXPECT_EQ(expected, *h); + EXPECT_EQ(expected, h.value()); + EXPECT_TRUE(dropped.empty()); + } + EXPECT_TRUE(reffed.empty()); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + { + const std::optional<int> maybeVal{expected}; + SimpleHandle h(maybeVal); + EXPECT_TRUE(h); + EXPECT_EQ(expected + 1, *h); + EXPECT_EQ(expected + 1, h.value()); + EXPECT_EQ(std::vector{expected}, reffed); + reffed.clear(); + EXPECT_TRUE(dropped.empty()); + } + EXPECT_EQ(std::vector{expected + 1}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, SimplePopulatedWithStorage) +{ + constexpr int expected = 3; + { + StoreHandle h(expected, std::string{"str"}, 5); + EXPECT_TRUE(h); + EXPECT_EQ(expected + 1, *h); + EXPECT_EQ(expected + 1, h.value()); + EXPECT_EQ(5, stored_ref); + EXPECT_EQ(std::vector{expected}, reffed); + reffed.clear(); + EXPECT_TRUE(dropped.empty()); + } + EXPECT_EQ(6, stored_drop); + EXPECT_EQ(std::vector{expected + 1}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, ResetPopulatedWithStorage) +{ + constexpr int expected = 3; + const std::string s{"str"}; + StoreHandle h(int{expected}, s, 5); + EXPECT_TRUE(dropped.empty()); + h.reset(std::nullopt); + EXPECT_FALSE(h); + EXPECT_THROW(h.value(), std::bad_optional_access); + EXPECT_EQ(5, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, ResetNewPopulated) +{ + constexpr int expected = 3, expected2 = 10; + { + SimpleHandle h(int{expected}); + EXPECT_TRUE(dropped.empty()); + h.reset(int{expected2}); + EXPECT_TRUE(h); + EXPECT_EQ(expected2, *h); + EXPECT_EQ(expected2, h.value()); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + } + EXPECT_EQ(std::vector{expected2}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, ResetCopyPopulated) +{ + constexpr int expected = 3, expected2 = 10; + { + SimpleHandle h(int{expected}); + EXPECT_TRUE(reffed.empty()); + EXPECT_TRUE(dropped.empty()); + const std::optional<int> maybe2{expected2}; + h.reset(maybe2); + EXPECT_TRUE(h); + EXPECT_EQ(expected2 + 1, *h); + EXPECT_EQ(expected2 + 1, h.value()); + EXPECT_EQ(std::vector{expected2}, reffed); + reffed.clear(); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + } + EXPECT_EQ(std::vector{expected2 + 1}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, ResetCopyPopulatedWithStorage) +{ + constexpr int expected = 3, expected2 = 10; + { + StoreHandle h(int{expected}, "str", 5); + EXPECT_TRUE(dropped.empty()); + h.reset(expected2); + EXPECT_TRUE(h); + EXPECT_EQ(expected2 + 1, *h); + EXPECT_EQ(expected2 + 1, h.value()); + EXPECT_EQ(5, stored_ref); + EXPECT_EQ(std::vector{expected2}, reffed); + reffed.clear(); + EXPECT_EQ(6, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + } + EXPECT_EQ(7, stored_drop); + EXPECT_EQ(std::vector{expected2 + 1}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, MoveConstructWithStorage) +{ + constexpr int expected = 3; + StoreHandle h1(int{expected}, "str", 5); + { + StoreHandle h2(std::move(h1)); + EXPECT_TRUE(dropped.empty()); + EXPECT_FALSE(h1); + EXPECT_THROW(h1.value(), std::bad_optional_access); + EXPECT_TRUE(h2); + EXPECT_EQ(expected, *h2); + EXPECT_EQ(expected, h2.value()); + } + EXPECT_EQ(5, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, MoveAssignWithStorage) +{ + constexpr int expected = 3, expected2 = 10; + { + StoreHandle h1(int{expected}, "str", 5); + StoreHandle h2(int{expected2}, "str", 10); + EXPECT_TRUE(dropped.empty()); + + h2 = std::move(h1); + EXPECT_EQ(10, stored_drop); + EXPECT_EQ(std::vector{expected2}, dropped); + dropped.clear(); + EXPECT_FALSE(h1); + EXPECT_THROW(h1.value(), std::bad_optional_access); + EXPECT_TRUE(h2); + EXPECT_EQ(expected, *h2); + EXPECT_EQ(expected, h2.value()); + } + EXPECT_EQ(5, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, CopyConstructSrcEmptyWithStorage) +{ + StoreHandle h1(std::nullopt, "str", 5); + StoreHandle h2(h1); +} + +TEST_F(CopyableHandleTest, CopyConstructWithStorage) +{ + constexpr int expected = 3; + StoreHandle h1(int{expected}, "str", 5); + StoreHandle h2(h1); + EXPECT_EQ(5, stored_ref); + EXPECT_EQ(std::vector{expected}, reffed); + reffed.clear(); + + h1.reset(); + EXPECT_EQ(5, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + h2.reset(); + EXPECT_EQ(6, stored_drop); + EXPECT_EQ(std::vector{expected + 1}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, CopyAssignBothEmptyWithStorage) +{ + StoreHandle h1(std::nullopt, "str", 5); + StoreHandle h2(std::nullopt, "str", 10); + h2 = h1; +} + +TEST_F(CopyableHandleTest, CopyAssignSrcEmptyWithStorage) +{ + constexpr int expected = 3; + StoreHandle h1(std::nullopt, "str", 5); + StoreHandle h2(int{expected}, "str", 10); + h2 = h1; + EXPECT_EQ(10, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, CopyAssignDstEmptyWithStorage) +{ + constexpr int expected = 3; + StoreHandle h1(int{expected}, "str", 5); + StoreHandle h2(std::nullopt, "str", 10); + h2 = h1; + EXPECT_EQ(5, stored_ref); + EXPECT_EQ(std::vector{expected}, reffed); + reffed.clear(); + + h1.reset(); + EXPECT_EQ(5, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + h2.reset(); + EXPECT_EQ(6, stored_drop); + EXPECT_EQ(std::vector{expected + 1}, dropped); + dropped.clear(); +} + +TEST_F(CopyableHandleTest, CopyAssignWithStorage) +{ + constexpr int expected = 3, expected2 = 15; + StoreHandle h1(int{expected}, "str", 5); + StoreHandle h2(int{expected2}, "str", 10); + h2 = h1; + EXPECT_EQ(10, stored_drop); + EXPECT_EQ(std::vector{expected2}, dropped); + dropped.clear(); + EXPECT_EQ(5, stored_ref); + EXPECT_EQ(std::vector{expected}, reffed); + reffed.clear(); + + h1.reset(); + EXPECT_EQ(5, stored_drop); + EXPECT_EQ(std::vector{expected}, dropped); + dropped.clear(); + h2.reset(); + EXPECT_EQ(6, stored_drop); + EXPECT_EQ(std::vector{expected + 1}, dropped); + dropped.clear(); +} + +} // namespace +} // namespace stdplus diff --git a/test/meson.build b/test/meson.build index 812de29..cc3d12d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -3,6 +3,7 @@ gmock = dependency('gmock', disabler: true, required: build_tests) tests = [ 'signal', + 'handle/copyable', 'handle/managed', ] |

