#pragma once #include namespace mbgl { /** * `Mutable` is a non-nullable uniquely owning reference to a `T`. It can be efficiently converted * to `Immutable`. * * The lifecycle of `Mutable` and `Immutable` is as follows: * * 1. Create a `Mutable` using `makeMutable(...)` * 2. Mutate it freely * 3. When you're ready to freeze its state and enable safe cross-thread sharing, move assign or * move construct it to `Immutable` * * The reason that `Mutable` exists, rather than simply using a `std::unique_ptr`, is to take advantage * of the underlying single-allocation optimization provided by `std::make_shared`. */ template class Mutable { public: Mutable(Mutable&&) = default; Mutable& operator=(Mutable&&) = default; Mutable(const Mutable&) = delete; Mutable& operator=(const Mutable&) = delete; T* get() { return ptr.get(); } T* operator->() { return ptr.get(); } T& operator*() { return *ptr; } private: Mutable(std::shared_ptr&& s) : ptr(std::move(s)) {} std::shared_ptr ptr; template friend class Immutable; template friend Mutable makeMutable(Args&&...); }; template Mutable makeMutable(Args&&... args) { return Mutable(std::make_shared(std::forward(args)...)); } /** * `Immutable` is a non-nullable shared reference to a `const T`. Construction requires * a transfer of unique ownership from a `Mutable`; once constructed it has the same behavior * as `std::shared_ptr` but with better indication of intent. * * Normally one should not share state between threads because it's difficult to verify the * absence of read/write data races. `Immutable` provides a guarantee that no writes are * possible, and instances therefore can be freely transferred and shared between threads. */ template class Immutable { public: template Immutable(Mutable&& s) : ptr(std::const_pointer_cast(std::move(s.ptr))) {} template Immutable(Immutable&& s) : ptr(std::move(s.ptr)) {} template Immutable(const Immutable& s) : ptr(s.ptr) {} template Immutable& operator=(Mutable&& s) { ptr = std::const_pointer_cast(std::move(s.ptr)); return *this; } template Immutable& operator=(Immutable&& s) { ptr = std::move(s.ptr); return *this; } template Immutable& operator=(const Immutable& s) { ptr = s.ptr; return *this; } const T* get() const { return ptr.get(); } const T* operator->() const { return ptr.get(); } const T& operator*() const { return *ptr; } friend bool operator==(const Immutable& lhs, const Immutable& rhs) { return lhs.ptr == rhs.ptr; } friend bool operator!=(const Immutable& lhs, const Immutable& rhs) { return lhs.ptr != rhs.ptr; } private: Immutable(std::shared_ptr&& s) : ptr(std::move(s)) {} std::shared_ptr ptr; template friend class Immutable; template friend Immutable staticImmutableCast(const Immutable&); }; template Immutable staticImmutableCast(const Immutable& u) { return Immutable(std::static_pointer_cast(u.ptr)); } /** * Constrained mutation of an immutable reference. Makes a temporarily-mutable copy of the * input Immutable using the inner type's copy constructor, runs the given callable on the * mutable copy, and then freezes the copy and reassigns it to the input reference. * * Note that other Immutables referring to the same inner instance are not affected; they * continue to referencing the original immutable instance. */ template void mutate(Immutable& immutable, Fn&& fn) { Mutable mut = makeMutable(*immutable); std::forward(fn)(*mut); immutable = std::move(mut); } } // namespace mbgl