summaryrefslogtreecommitdiff
path: root/include/mbgl/util/immutable.hpp
blob: 683af55c57acb38c9c0aaeb619bb049a567c7e9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#pragma once

#include <memory>

namespace mbgl {

template <class T>
class Immutable;

/**
 * `Mutable<T>` is a non-nullable uniquely owning reference to a `T`. It can be efficiently converted
 * to `Immutable<T>`.
 * 
 * The lifecycle of `Mutable<T>` and `Immutable<T>` is as follows:
 * 
 *   1. Create a `Mutable<T>` 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<T>`
 *
 * The reason that `Mutable<T>` exists, rather than simply using a `std::unique_ptr<T>`, is to take advantage
 * of the underlying single-allocation optimization provided by `std::make_shared`.
 */
template <class T>
class Mutable {
public:
    Mutable(Mutable&&) noexcept = default;
    Mutable& operator=(Mutable&&) noexcept = 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<T>&& s)
        : ptr(std::move(s)) {}

    std::shared_ptr<T> ptr;

    template <class S> friend class Immutable;
    // NOLINTNEXTLINE(readability-redundant-declaration)
    template <class S, class... Args> friend Mutable<S> makeMutable(Args&&...);
    // NOLINTNEXTLINE(readability-redundant-declaration)
    template <class S, class U> friend Mutable<S> staticMutableCast(const Mutable<U>&);
    // NOLINTNEXTLINE(readability-redundant-declaration)
    template <class S, class U>
    friend Mutable<S> constImmutableCast(const Immutable<U>&);
};

template <class T, class... Args>
Mutable<T> makeMutable(Args&&... args) {
    return Mutable<T>(std::make_shared<T>(std::forward<Args>(args)...));
}

template <class S, class U>
Mutable<S> staticMutableCast(const Mutable<U>& u) {
    return Mutable<S>(std::static_pointer_cast<S>(u.ptr));
}

/**
 * `Immutable<T>` is a non-nullable shared reference to a `const T`. Construction requires
 * a transfer of unique ownership from a `Mutable<T>`; once constructed it has the same behavior
 * as `std::shared_ptr<const T>` 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 T>
class Immutable {
public:
    template <class S>
    Immutable(Mutable<S>&& s)
        : ptr(std::const_pointer_cast<const S>(std::move(s.ptr))) {}

    template <class S>
    Immutable(Immutable<S> s)
        : ptr(std::move(s.ptr)) {}

    Immutable(Immutable&&) noexcept = default;
    Immutable(const Immutable&) = default;

    template <class S>
    Immutable& operator=(Mutable<S>&& s) {
        ptr = std::const_pointer_cast<const S>(std::move(s.ptr));
        return *this;
    }

    auto useCount() const { return ptr.use_count(); }

    Immutable& operator=(Immutable&&) noexcept = default;
    Immutable& operator=(const Immutable&) = default;

    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<T>& lhs, const Immutable<T>& rhs) {
        return lhs.ptr == rhs.ptr;
    }

    friend bool operator!=(const Immutable<T>& lhs, const Immutable<T>& rhs) {
        return lhs.ptr != rhs.ptr;
    }

private:
    Immutable(std::shared_ptr<const T>&& s)
        : ptr(std::move(s)) {}

    std::shared_ptr<const T> ptr;

    template <class S> friend class Immutable;

    // NOLINTNEXTLINE(readability-redundant-declaration)
    template <class S, class U> friend Immutable<S> staticImmutableCast(const Immutable<U>&);

    template <class S, class U>
    friend Mutable<S> constImmutableCast(const Immutable<U>&);
};

template <class S, class U>
Immutable<S> staticImmutableCast(const Immutable<U>& u) {
    return Immutable<S>(std::static_pointer_cast<const S>(u.ptr));
}

// Note: this function is breaking the intended Mutable/Immutable design,
// so its usage shall be limited.
// The immutable object must be managed only by |u|.
template <class S, class U>
Mutable<S> constImmutableCast(const Immutable<U>& u) {
    assert(u.useCount() == 1u);
    return Mutable<S>(std::static_pointer_cast<S>(std::const_pointer_cast<U>(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 <class T, class Fn>
void mutate(Immutable<T>& immutable, Fn&& fn) {
    Mutable<T> mut = makeMutable<T>(*immutable);
    std::forward<Fn>(fn)(*mut);
    immutable = std::move(mut);
}

} // namespace mbgl