1 | #pragma once |
2 | |
3 | #include <memory> |
4 | |
5 | namespace mbgl { |
6 | |
7 | /** |
8 | * `Mutable<T>` is a non-nullable uniquely owning reference to a `T`. It can be efficiently converted |
9 | * to `Immutable<T>`. |
10 | * |
11 | * The lifecycle of `Mutable<T>` and `Immutable<T>` is as follows: |
12 | * |
13 | * 1. Create a `Mutable<T>` using `makeMutable(...)` |
14 | * 2. Mutate it freely |
15 | * 3. When you're ready to freeze its state and enable safe cross-thread sharing, move assign or |
16 | * move construct it to `Immutable<T>` |
17 | * |
18 | * The reason that `Mutable<T>` exists, rather than simply using a `std::unique_ptr<T>`, is to take advantage |
19 | * of the underlying single-allocation optimization provided by `std::make_shared`. |
20 | */ |
21 | template <class T> |
22 | class Mutable { |
23 | public: |
24 | Mutable(Mutable&&) = default; |
25 | Mutable& operator=(Mutable&&) = default; |
26 | |
27 | Mutable(const Mutable&) = delete; |
28 | Mutable& operator=(const Mutable&) = delete; |
29 | |
30 | T* get() { return ptr.get(); } |
31 | T* operator->() { return ptr.get(); } |
32 | T& operator*() { return *ptr; } |
33 | |
34 | private: |
35 | Mutable(std::shared_ptr<T>&& s) |
36 | : ptr(std::move(s)) {} |
37 | |
38 | std::shared_ptr<T> ptr; |
39 | |
40 | template <class S> friend class Immutable; |
41 | template <class S, class... Args> friend Mutable<S> makeMutable(Args&&...); |
42 | }; |
43 | |
44 | template <class T, class... Args> |
45 | Mutable<T> makeMutable(Args&&... args) { |
46 | return Mutable<T>(std::make_shared<T>(std::forward<Args>(args)...)); |
47 | } |
48 | |
49 | /** |
50 | * `Immutable<T>` is a non-nullable shared reference to a `const T`. Construction requires |
51 | * a transfer of unique ownership from a `Mutable<T>`; once constructed it has the same behavior |
52 | * as `std::shared_ptr<const T>` but with better indication of intent. |
53 | * |
54 | * Normally one should not share state between threads because it's difficult to verify the |
55 | * absence of read/write data races. `Immutable` provides a guarantee that no writes are |
56 | * possible, and instances therefore can be freely transferred and shared between threads. |
57 | */ |
58 | template <class T> |
59 | class Immutable { |
60 | public: |
61 | template <class S> |
62 | Immutable(Mutable<S>&& s) |
63 | : ptr(std::const_pointer_cast<const S>(std::move(s.ptr))) {} |
64 | |
65 | template <class S> |
66 | Immutable(Immutable<S>&& s) |
67 | : ptr(std::move(s.ptr)) {} |
68 | |
69 | template <class S> |
70 | Immutable(const Immutable<S>& s) |
71 | : ptr(s.ptr) {} |
72 | |
73 | template <class S> |
74 | Immutable& operator=(Mutable<S>&& s) { |
75 | ptr = std::const_pointer_cast<const S>(std::move(s.ptr)); |
76 | return *this; |
77 | } |
78 | |
79 | template <class S> |
80 | Immutable& operator=(Immutable<S>&& s) { |
81 | ptr = std::move(s.ptr); |
82 | return *this; |
83 | } |
84 | |
85 | template <class S> |
86 | Immutable& operator=(const Immutable<S>& s) { |
87 | ptr = s.ptr; |
88 | return *this; |
89 | } |
90 | |
91 | const T* get() const { return ptr.get(); } |
92 | const T* operator->() const { return ptr.get(); } |
93 | const T& operator*() const { return *ptr; } |
94 | |
95 | friend bool operator==(const Immutable<T>& lhs, const Immutable<T>& rhs) { |
96 | return lhs.ptr == rhs.ptr; |
97 | } |
98 | |
99 | friend bool operator!=(const Immutable<T>& lhs, const Immutable<T>& rhs) { |
100 | return lhs.ptr != rhs.ptr; |
101 | } |
102 | |
103 | private: |
104 | Immutable(std::shared_ptr<const T>&& s) |
105 | : ptr(std::move(s)) {} |
106 | |
107 | std::shared_ptr<const T> ptr; |
108 | |
109 | template <class S> friend class Immutable; |
110 | template <class S, class U> friend Immutable<S> staticImmutableCast(const Immutable<U>&); |
111 | }; |
112 | |
113 | template <class S, class U> |
114 | Immutable<S> staticImmutableCast(const Immutable<U>& u) { |
115 | return Immutable<S>(std::static_pointer_cast<const S>(u.ptr)); |
116 | } |
117 | |
118 | /** |
119 | * Constrained mutation of an immutable reference. Makes a temporarily-mutable copy of the |
120 | * input Immutable using the inner type's copy constructor, runs the given callable on the |
121 | * mutable copy, and then freezes the copy and reassigns it to the input reference. |
122 | * |
123 | * Note that other Immutables referring to the same inner instance are not affected; they |
124 | * continue to referencing the original immutable instance. |
125 | */ |
126 | template <class T, class Fn> |
127 | void mutate(Immutable<T>& immutable, Fn&& fn) { |
128 | Mutable<T> mut = makeMutable<T>(*immutable); |
129 | std::forward<Fn>(fn)(*mut); |
130 | immutable = std::move(mut); |
131 | } |
132 | |
133 | } // namespace mbgl |
134 | |