From 4cf930d26ad9f46fb9cc733d57a23f20f6f4186a Mon Sep 17 00:00:00 2001 From: Giel van Schijndel Date: Sun, 17 Dec 2017 14:07:25 +0100 Subject: [PATCH] Add observer_ptr: like weak_ptr without requiring shared_ptr --- observer_ptr.hpp | 330 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 observer_ptr.hpp diff --git a/observer_ptr.hpp b/observer_ptr.hpp new file mode 100644 index 0000000..658d442 --- /dev/null +++ b/observer_ptr.hpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2017 Giel van Schijndel + * + * 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 3 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, see + * . + */ + +#ifndef INCLUDED_OBSERVER_PTR_HPP +#define INCLUDED_OBSERVER_PTR_HPP + +#include +#include +#include +#include + +namespace util { + +// Implementation details, not part of public API +namespace detail { + class observable_deleter_base; + + struct observer_data + { + void* p = nullptr; + observable_deleter_base* d = nullptr; + }; + + class observable_deleter_base + { + public: + observable_deleter_base() = default; + + observable_deleter_base(observable_deleter_base&& rhs) noexcept : observers([&rhs] { + std::lock_guard lock{rhs.mutex}; + return decltype(rhs.observers){std::move(rhs.observers)}; + }()) + { + } + + observable_deleter_base& operator=(observable_deleter_base rhs) noexcept + { + std::lock_guard lock{mutex}; + assert(observers.empty()); + observers = std::move(rhs.observers); + return *this; + } + + ~observable_deleter_base() + { + assert(observers.empty()); + } + + void wipe() noexcept + { + std::lock_guard lock{mutex}; + + for (auto& observer : observers) + { + assert(observer->d == this); + observer->p = nullptr; + observer->d = nullptr; + } + observers.clear(); + } + + void push(observer_data& o) noexcept + { + const auto observer_inserted = [&] { + std::lock_guard lock{mutex}; + return observers.insert(&o).second; + }(); + assert(observer_inserted); + + if (observer_inserted) + { + o.d = this; + } + } + + void pop(observer_data& o) noexcept + { + const auto count_erased = [&] { + std::lock_guard lock{mutex}; + return observers.erase(&o); + }(); + assert(count_erased > 0); + + if (count_erased > 0) + { + o.p = nullptr; + o.d = nullptr; + } + } + + private: + // Yes this might be possible to do more efficiently with atomics, but will also be _much_ more + // error prone. + std::mutex mutex; + std::unordered_set observers; + }; +} + +template +class observer_ptr : private detail::observer_data +{ +public: + class observable_deleter final : public detail::observable_deleter_base + { + public: + void operator()(T* p) noexcept(noexcept(delete p)) + { + static_assert(!std::is_void::value, "can't delete pointer to incomplete type"); + static_assert(sizeof(T) > 0, "can't delete pointer to incomplete type"); + + wipe(); + delete p; + } + }; + + constexpr observer_ptr() noexcept = default; + constexpr observer_ptr(std::nullptr_t) noexcept + { + } + + /// Convert from compatible unique_ptr + template ::value>::type> + observer_ptr(std::unique_ptr::observable_deleter>& rhs) noexcept + { + if (rhs) + { + this->p = static_cast(rhs.get()); + rhs.get_deleter().push(*this); + } + } + + observer_ptr(const observer_ptr& rhs) noexcept + { + if (rhs) + { + this->p = static_cast(rhs.get()); + rhs.d->push(*this); + } + } + + template ::value + && !std::is_same::value>::type> + observer_ptr(const observer_ptr& rhs) noexcept + { + if (rhs) + { + this->p = static_cast(rhs.get()); + rhs.d->push(*this); + } + } + + template + observer_ptr(const observer_ptr& rhs, T* const rp) noexcept + { + assert(rp != nullptr); + assert(rhs.d != nullptr); + + this->p = rp; + rhs.d->push(*this); + } + + observer_ptr& operator=(const observer_ptr& rhs) noexcept + { + if (this->p == rhs.p) + return *this; + + reset(); + if (rhs) + { + this->p = static_cast(rhs.get()); + rhs.d->push(*this); + } + + return *this; + } + + ~observer_ptr() noexcept + { + reset(); + } + + void reset() + { + if (d) + { + d->pop(*this); + } + } + + constexpr T* get() const noexcept + { + return static_cast(this->p); + } + + constexpr explicit operator bool() const + { + return this->p != nullptr; + } + + constexpr T& operator*() const noexcept + { + assert(this->p != nullptr); + return *get(); + } + + constexpr T* operator->() const noexcept + { + assert(this->p != nullptr); + return get(); + } + +private: + template + friend class observer_ptr; +}; + +template +using observable_ptr = std::unique_ptr::observable_deleter>; + +template +observable_ptr make_observable(Args&&... args) +{ + return observable_ptr{new T(std::forward(args)...)}; +} + +template +observer_ptr dynamic_pointer_cast(const observer_ptr& r) noexcept +{ + if (auto* p = dynamic_cast(r.get())) + return observer_ptr{r, p}; + return {}; +} + +template +constexpr bool operator==(const observer_ptr& lhs, const observer_ptr& rhs) noexcept +{ + return lhs.get() == rhs.get(); +} + +template +constexpr bool operator==(const observer_ptr& lhs, T2* rhs) noexcept +{ + return lhs.get() == rhs; +} + +template +constexpr bool operator==(T1* lhs, const observer_ptr& rhs) noexcept +{ + return lhs == rhs.get(); +} + +template +constexpr bool operator==(const observer_ptr& lhs, std::nullptr_t) noexcept +{ + return !lhs; +} + +template +constexpr bool operator==(std::nullptr_t, const observer_ptr& rhs) noexcept +{ + return !rhs; +} + +template +constexpr bool operator!=(const observer_ptr& lhs, const observer_ptr& rhs) noexcept +{ + return !(lhs == rhs); +} + +template +constexpr bool operator!=(const observer_ptr& lhs, T2* rhs) noexcept +{ + return !(lhs == rhs); +} + +template +constexpr bool operator!=(T1* lhs, const observer_ptr& rhs) noexcept +{ + return !(lhs == rhs); +} + +template +constexpr bool operator!=(const observer_ptr& lhs, std::nullptr_t) noexcept +{ + return static_cast(lhs); +} + +template +constexpr bool operator!=(std::nullptr_t, const observer_ptr& rhs) noexcept +{ + return static_cast(rhs); +} + +template +constexpr bool operator<(const observer_ptr& lhs, const observer_ptr& rhs) noexcept +{ + return lhs.get() < rhs.get(); +} + +template +constexpr bool operator<(const observer_ptr& lhs, T2* rhs) noexcept +{ + return lhs.get() < rhs; +} + +template +constexpr bool operator<(T1* lhs, const observer_ptr& rhs) noexcept +{ + return lhs < rhs.get(); +} +} + +#endif /* INCLUDED_OBSERVER_PTR_HPP */