handle/managed: Implement non-copyable handle
This is a generic handle type that holds a resource and uses RAII to
call a user defined function when the resource is destroyed. A future
change will implement a smart file descriptor based on this interface.
A follow up change will implement the copyable version.
Tested:
Built and run through unit tests.
Change-Id: Ia8da1d662319e8fb58380ed4979bcf1b74f66dfb
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index 5e935f4..57af82a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,5 +4,7 @@
libstdplus_la_SOURCES =
libstdplus_la_LIBADD = $(COMMON_LIBS)
+nobase_include_HEADERS += stdplus/handle/managed.hpp
+
nobase_include_HEADERS += stdplus/signal.hpp
libstdplus_la_SOURCES += stdplus/signal.cpp
diff --git a/src/meson.build b/src/meson.build
index fd56941..6b1fea9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -18,3 +18,7 @@
install_headers(
'stdplus/signal.hpp',
subdir: 'stdplus')
+
+install_headers(
+ 'stdplus/handle/managed.hpp',
+ subdir: 'stdplus/handle')
diff --git a/src/stdplus/handle/managed.hpp b/src/stdplus/handle/managed.hpp
new file mode 100644
index 0000000..361c1ec
--- /dev/null
+++ b/src/stdplus/handle/managed.hpp
@@ -0,0 +1,183 @@
+#pragma once
+#include <cstdlib>
+#include <optional>
+#include <tuple>
+#include <utility>
+
+namespace stdplus
+{
+
+/** @brief An RAII handle which takes an object and calls a user specified
+ * function on the object when it should be cleaned up.
+ * @details This is useful for adding RAII semantics to non-RAII things like
+ * file descriptors, or c structs allocated with special routines.
+ * We could make a simple file descriptor wrapper that is
+ * automatically closed:
+ *
+ * void closefd(int&& fd) { close(fd); }
+ * using Fd = Managed<int>::Handle<closefd>;
+ *
+ * void some_func()
+ * {
+ * Fd fd(open("somefile", 0));
+ * char buf[4096];
+ * int amt = read(*fd, buf, sizeof(buf));
+ * printf("%.*s\n", amt, data);
+ * }
+ */
+template <typename T, typename... As>
+struct Managed
+{
+ template <void (*drop)(T&&, As&...)>
+ class Handle
+ {
+ public:
+ /** @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 :
+ as(std::forward<Vs>(vs)...),
+ maybeT(std::move(maybeV))
+ {
+ }
+ template <typename... Vs>
+ constexpr explicit Handle(T&& maybeV, Vs&&... vs) noexcept :
+ as(std::forward<Vs>(vs)...), maybeT(std::move(maybeV))
+ {
+ }
+
+ Handle(const Handle& other) = delete;
+ Handle& operator=(const Handle& other) = delete;
+
+ constexpr Handle(Handle&& other) :
+ as(std::move(other.as)), maybeT(std::move(other.maybeT))
+ {
+ other.maybeT = std::nullopt;
+ }
+
+ constexpr Handle& operator=(Handle&& other)
+ {
+ if (this != &other)
+ {
+ reset(std::move(other.maybeT));
+ as = std::move(other.as);
+ other.maybeT = std::nullopt;
+ }
+ return *this;
+ }
+
+ virtual ~Handle()
+ {
+ try
+ {
+ reset();
+ }
+ catch (...)
+ {
+ std::abort();
+ }
+ }
+
+ /** @brief Gets the managed object
+ *
+ * @return A pointer to the object
+ */
+ constexpr const T* operator->() const noexcept
+ {
+ return &(*maybeT);
+ }
+
+ /** @brief Gets the managed object
+ *
+ * @return A reference to the object
+ */
+ constexpr const T& operator*() const& noexcept
+ {
+ return *maybeT;
+ }
+
+ /** @brief Determine if we are managing an object
+ *
+ * @return Do we currently have an object
+ */
+ constexpr explicit operator bool() const noexcept
+ {
+ return static_cast<bool>(maybeT);
+ }
+
+ /** @brief Determine if we are managing an object
+ *
+ * @return Do we currently have an object
+ */
+ constexpr bool has_value() const noexcept
+ {
+ return maybeT.has_value();
+ }
+
+ /** @brief Gets the managed object
+ *
+ * @throws std::bad_optional_access if it has no object
+ * @return A reference to the managed object
+ */
+ constexpr const T& value() const&
+ {
+ return maybeT.value();
+ }
+
+ /** @brief Gets the managed object if it exists
+ *
+ * @throws std::bad_optional_access if it has no object
+ * @return A reference to the managed object
+ */
+ constexpr const std::optional<T>& maybe_value() const& noexcept
+ {
+ return maybeT;
+ }
+
+ /** @brief Resets the managed value to a new value
+ * The container takes ownership of the value
+ *
+ * @param[in] maybeV - Maybe the new value
+ */
+ constexpr void reset(std::optional<T>&& maybeV)
+ {
+ maybeDrop(std::index_sequence_for<As...>());
+ maybeT = std::move(maybeV);
+ }
+ constexpr void reset(T&& maybeV)
+ {
+ maybeDrop(std::index_sequence_for<As...>());
+ maybeT = std::move(maybeV);
+ }
+
+ /** @brief A shorthand reset function for convenience
+ * Same as calling reset(std::nullopt)
+ */
+ constexpr void reset()
+ {
+ reset(std::nullopt);
+ }
+
+ protected:
+ /* Hold the data parameterized for this container */
+ std::tuple<As...> as;
+
+ private:
+ /* Stores the managed object if we have one */
+ std::optional<T> maybeT;
+
+ template <size_t... Indices>
+ void maybeDrop(std::index_sequence<Indices...>)
+ {
+ if (maybeT)
+ {
+ drop(std::move(*maybeT), std::get<Indices>(as)...);
+ }
+ }
+ };
+};
+
+} // namespace stdplus