// // immer: immutable data structures for C++ // Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente // // This software is distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // #pragma once #include #include #include #include namespace immer { namespace detail { template struct refcount_atom_impl { using box_type = box; using value_type = T; using memory_policy = MemoryPolicy; using spinlock_t = typename MemoryPolicy::refcount::spinlock_type; using scoped_lock_t = typename spinlock_t::scoped_lock; refcount_atom_impl(const refcount_atom_impl&) = delete; refcount_atom_impl(refcount_atom_impl&&) = delete; refcount_atom_impl& operator=(const refcount_atom_impl&) = delete; refcount_atom_impl& operator=(refcount_atom_impl&&) = delete; refcount_atom_impl(box_type b) : impl_{std::move(b)} {} box_type load() const { scoped_lock_t lock{lock_}; return impl_; } void store(box_type b) { scoped_lock_t lock{lock_}; impl_ = std::move(b); } box_type exchange(box_type b) { { scoped_lock_t lock{lock_}; swap(b, impl_); } return b; } template box_type update(Fn&& fn) { while (true) { auto oldv = load(); auto newv = oldv.update(fn); { scoped_lock_t lock{lock_}; if (oldv.impl_ == impl_.impl_) { impl_ = newv; return {newv}; } } } } private: mutable spinlock_t lock_; box_type impl_; }; template struct gc_atom_impl { using box_type = box; using value_type = T; using memory_policy = MemoryPolicy; static_assert(std::is_same::value, "gc_atom_impl can only be used when there is no refcount!"); gc_atom_impl(const gc_atom_impl&) = delete; gc_atom_impl(gc_atom_impl&&) = delete; gc_atom_impl& operator=(const gc_atom_impl&) = delete; gc_atom_impl& operator=(gc_atom_impl&&) = delete; gc_atom_impl(box_type b) : impl_{b.impl_} {} box_type load() const { return {impl_.load()}; } void store(box_type b) { impl_.store(b.impl_); } box_type exchange(box_type b) { return {impl_.exchange(b.impl_)}; } template box_type update(Fn&& fn) { while (true) { auto oldv = box_type{impl_.load()}; auto newv = oldv.update(fn); if (impl_.compare_exchange_weak(oldv.impl_, newv.impl_)) return {newv}; } } private: std::atomic impl_; }; } // namespace detail /*! * Stores for boxed values of type `T` in a thread-safe manner. * * @see box * * @rst * * .. warning:: If memory policy used includes thread unsafe reference counting, * no no thread safety is assumed, and the atom becomes thread unsafe too! * * .. note:: ``box`` provides a value based box of type ``T``, this is, we * can think about it as a value-based version of ``std::shared_ptr``. In a * similar fashion, ``atom`` is in spirit the value-based equivalent of * C++20 ``std::atomic_shared_ptr``. However, the API does not follow * ``std::atomic`` interface closely, since it attempts to be a higher level * construction, most similar to Clojure's ``(atom)``. It is remarkable in * particular that, since ``box`` underlying object is immutable, using * ``atom`` is fully thread-safe in ways that ``std::atmic_shared_ptr`` is * not. This is so because dereferencing the underlying pointer in a * ``std::atomic_share_ptr`` may require further synchronization, in * particular when invoking non-const methods. * * @endrst */ template class atom { public: using box_type = box; using value_type = T; using memory_policy = MemoryPolicy; atom(const atom&) = delete; atom(atom&&) = delete; void operator=(const atom&) = delete; void operator=(atom&&) = delete; /*! * Constructs an atom holding a value `b`; */ atom(box_type v = {}) : impl_{std::move(v)} {} /*! * Sets a new value in the atom. */ atom& operator=(box_type b) { impl_.store(std::move(b)); return *this; } /*! * Reads the currently stored value in a thread-safe manner. */ operator box_type() const { return impl_.load(); } /*! * Reads the currently stored value in a thread-safe manner. */ operator value_type() const { return *impl_.load(); } /*! * Reads the currently stored value in a thread-safe manner. */ IMMER_NODISCARD box_type load() const { return impl_.load(); } /*! * Stores a new value in a thread-safe manner. */ void store(box_type b) { impl_.store(std::move(b)); } /*! * Stores a new value and returns the old value, in a thread-safe manner. */ IMMER_NODISCARD box_type exchange(box_type b) { return impl_.exchange(std::move(b)); } /*! * Stores the result of applying `fn` to the current value atomically and * returns the new resulting value. * * @rst * * .. warning:: ``fn`` must be a pure function and have no side effects! The * function might be evaluated multiple times when multiple threads * content to update the value. * * @endrst */ template box_type update(Fn&& fn) { return impl_.update(std::forward(fn)); } private: struct get_refcount_atom_impl { template struct apply { using type = detail::refcount_atom_impl; }; }; struct get_gc_atom_impl { template struct apply { using type = detail::gc_atom_impl; }; }; // If we are using "real" garbage collection (we assume this when we use // `no_refcount_policy`), we just store the pointer in an atomic. If we use // reference counting, we rely on the reference counting spinlock. using impl_t = typename std::conditional_t< std::is_same::value, get_gc_atom_impl, get_refcount_atom_impl>::template apply::type; impl_t impl_; }; } // namespace immer