//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #ifndef REALM_ATOMIC_SHARED_PTR_HPP #define REALM_ATOMIC_SHARED_PTR_HPP #include <atomic> #include <memory> #include <mutex> #include <type_traits> namespace realm { namespace _impl { // Check if std::atomic_load has an overload taking a std::shared_ptr, and set // HasAtomicPtrOps to either true_type or false_type template <typename, typename = std::void_t<>> struct HasAtomicPtrOps : std::false_type { }; template <class T> struct HasAtomicPtrOps<T, std::void_t<decltype(std::atomic_load(std::declval<T*>()))>> : std::true_type { }; } // namespace _impl namespace util { // A wrapper for std::shared_ptr that enables sharing a shared_ptr instance // (and not just a thing *pointed to* by a shared_ptr) between threads. Is // lock-free iff the underlying shared_ptr implementation supports atomic // operations. Currently the only implemented operation other than copy/move // construction/assignment is exchange(). template <typename T, bool = realm::_impl::HasAtomicPtrOps<std::shared_ptr<T>>::value> class AtomicSharedPtr; template <typename T> class AtomicSharedPtr<T, true> { public: AtomicSharedPtr() = default; AtomicSharedPtr(std::shared_ptr<T> ptr) : m_ptr(std::move(ptr)) { } AtomicSharedPtr(AtomicSharedPtr const& ptr) : m_ptr(std::atomic_load(&ptr.m_ptr)) { } AtomicSharedPtr(AtomicSharedPtr&& ptr) : m_ptr(std::atomic_exchange(&ptr.m_ptr, {})) { } AtomicSharedPtr& operator=(AtomicSharedPtr const& ptr) { if (&ptr != this) { std::atomic_store(&m_ptr, std::atomic_load(&ptr.m_ptr)); } return *this; } AtomicSharedPtr& operator=(AtomicSharedPtr&& ptr) { std::atomic_store(&m_ptr, std::atomic_exchange(&ptr.m_ptr, {})); return *this; } std::shared_ptr<T> exchange(std::shared_ptr<T> ptr) { return std::atomic_exchange(&m_ptr, std::move(ptr)); } std::shared_ptr<T> load() const noexcept { return std::atomic_load(&m_ptr); } private: std::shared_ptr<T> m_ptr = nullptr; }; template <typename T> class AtomicSharedPtr<T, false> { public: AtomicSharedPtr() = default; AtomicSharedPtr(std::shared_ptr<T> ptr) : m_ptr(std::move(ptr)) { } AtomicSharedPtr(AtomicSharedPtr const& ptr) { std::lock_guard<std::mutex> lock(ptr.m_mutex); m_ptr = ptr.m_ptr; } AtomicSharedPtr(AtomicSharedPtr&& ptr) { std::lock_guard<std::mutex> lock(ptr.m_mutex); m_ptr = std::move(ptr.m_ptr); } AtomicSharedPtr& operator=(AtomicSharedPtr const& ptr) { if (&ptr != this) { // std::lock() ensures that these are locked in a consistent order // to avoid deadlock std::lock(m_mutex, ptr.m_mutex); m_ptr = ptr.m_ptr; m_mutex.unlock(); ptr.m_mutex.unlock(); } return *this; } AtomicSharedPtr& operator=(AtomicSharedPtr&& ptr) { std::lock(m_mutex, ptr.m_mutex); m_ptr = std::move(ptr.m_ptr); m_mutex.unlock(); ptr.m_mutex.unlock(); return *this; } std::shared_ptr<T> exchange(std::shared_ptr<T> ptr) { std::lock_guard<std::mutex> lock(m_mutex); m_ptr.swap(ptr); return ptr; } std::shared_ptr<T> load() const noexcept { std::lock_guard<std::mutex> lock(m_mutex); return m_ptr; } private: mutable std::mutex m_mutex; std::shared_ptr<T> m_ptr = nullptr; }; } // namespace util } // namespace realm #endif // REALM_ATOMIC_SHARED_PTR_HPP