1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4#pragma once
5#include "slint_sharedvector_internal.h"
6#include <atomic>
7#include <algorithm>
8#include <initializer_list>
9#include <memory>
10
11namespace slint {
12
13/// SharedVector is a vector template class similar to std::vector that's primarily used for passing
14/// data in and out of the Slint run-time library. It uses implicit-sharing to make creating
15/// copies cheap. Only when a function changes the vector's data, a copy is is made.
16template<typename T>
17struct SharedVector
18{
19 /// Creates a new, empty vector.
20 SharedVector()
21 : inner(const_cast<SharedVectorHeader *>(reinterpret_cast<const SharedVectorHeader *>(
22 cbindgen_private::slint_shared_vector_empty())))
23 {
24 }
25
26 /// Creates a new vector that holds all the elements of the given std::initializer_list \a args.
27 SharedVector(std::initializer_list<T> args)
28 : SharedVector(SharedVector::with_capacity(capacity: args.size()))
29 {
30 auto new_data = reinterpret_cast<T *>(inner + 1);
31 auto input_it = args.begin();
32 for (std::size_t i = 0; i < args.size(); ++i, ++input_it) {
33 new (new_data + i) T(*input_it);
34 inner->size++;
35 }
36 }
37
38 /// Creates a vector of a given size, with default-constructed data.
39 explicit SharedVector(size_t size) : SharedVector(SharedVector::with_capacity(capacity: size))
40 {
41 auto new_data = reinterpret_cast<T *>(inner + 1);
42 for (std::size_t i = 0; i < size; ++i) {
43 new (new_data + i) T();
44 inner->size++;
45 }
46 }
47
48 /// Creates a vector of a given size, initialized with copies of the \a value.
49 explicit SharedVector(size_t size, const T &value)
50 : SharedVector(SharedVector::with_capacity(capacity: size))
51 {
52 auto new_data = reinterpret_cast<T *>(inner + 1);
53 for (std::size_t i = 0; i < size; ++i) {
54 new (new_data + i) T(value);
55 inner->size++;
56 }
57 }
58
59 /// Constructs the container with the contents of the range `[first, last)`.
60 template<class InputIt>
61 SharedVector(InputIt first, InputIt last)
62 : SharedVector(SharedVector::with_capacity(capacity: std::distance(first, last)))
63 {
64 std::uninitialized_copy(first, last, begin());
65 inner->size = inner->capacity;
66 }
67
68 /// Creates a new vector that is a copy of \a other.
69 SharedVector(const SharedVector &other) : inner(other.inner)
70 {
71 if (inner->refcount > 0) {
72 ++inner->refcount;
73 }
74 }
75
76 /// Destroys this vector. The underlying data is destroyed if no other
77 /// vector references it.
78 ~SharedVector() { drop(); }
79 /// Assigns the data of \a other to this vector and returns a reference to this vector.
80 SharedVector &operator=(const SharedVector &other)
81 {
82 if (other.inner == inner) {
83 return *this;
84 }
85 drop();
86 inner = other.inner;
87 if (inner->refcount > 0) {
88 ++inner->refcount;
89 }
90 return *this;
91 }
92 /// Move-assign's \a other to this vector and returns a reference to this vector.
93 SharedVector &operator=(SharedVector &&other)
94 {
95 std::swap(inner, other.inner);
96 return *this;
97 }
98
99 /// Returns a const pointer to the first element of this vector.
100 const T *cbegin() const { return reinterpret_cast<const T *>(inner + 1); }
101
102 /// Returns a const pointer that points past the last element of this vector. The
103 /// pointer cannot be dereferenced, it can only be used for comparison.
104 const T *cend() const { return cbegin() + inner->size; }
105
106 /// Returns a const pointer to the first element of this vector.
107 const T *begin() const { return cbegin(); }
108 /// Returns a const pointer that points past the last element of this vector. The
109 /// pointer cannot be dereferenced, it can only be used for comparison.
110 const T *end() const { return cend(); }
111
112 /// Returns a pointer to the first element of this vector.
113 T *begin()
114 {
115 detach(expected_capacity: inner->size);
116 return reinterpret_cast<T *>(inner + 1);
117 }
118
119 /// Returns a pointer that points past the last element of this vector. The
120 /// pointer cannot be dereferenced, it can only be used for comparison.
121 T *end()
122 {
123 detach(expected_capacity: inner->size);
124 return begin() + inner->size;
125 }
126
127 /// Returns the number of elements in this vector.
128 std::size_t size() const { return inner->size; }
129
130 /// Returns true if there are no elements on this vector; false otherwise.
131 bool empty() const { return inner->size == 0; }
132
133 /// This indexing operator returns a reference to the \a `index`th element of this vector.
134 T &operator[](std::size_t index) { return begin()[index]; }
135 /// This indexing operator returns a const reference to the \a `index`th element of this vector.
136 const T &operator[](std::size_t index) const { return begin()[index]; }
137
138 /// Returns a reference to the \a `index`th element of this vector.
139 const T &at(std::size_t index) const { return begin()[index]; }
140
141 /// Appends the \a value as a new element to the end of this vector.
142 void push_back(const T &value)
143 {
144 detach(expected_capacity: inner->size + 1);
145 new (end()) T(value);
146 inner->size++;
147 }
148 /// Moves the \a value as a new element to the end of this vector.
149 void push_back(T &&value)
150 {
151 detach(expected_capacity: inner->size + 1);
152 new (end()) T(std::move(value));
153 inner->size++;
154 }
155
156 /// Clears the vector and removes all elements. The capacity remains unaffected.
157 void clear()
158 {
159 if (inner->refcount != 1) {
160 *this = SharedVector();
161 } else {
162 auto b = cbegin(), e = cend();
163 inner->size = 0;
164 for (auto it = b; it < e; ++it) {
165 it->~T();
166 }
167 }
168 }
169
170 /// Returns true if the vector \a a has the same number of elements as \a b
171 /// and all the elements also compare equal; false otherwise.
172 friend bool operator==(const SharedVector &a, const SharedVector &b)
173 {
174 if (a.size() != b.size())
175 return false;
176 return std::equal(a.cbegin(), a.cend(), b.cbegin());
177 }
178
179 /// \private
180 std::size_t capacity() const { return inner->capacity; }
181
182private:
183 void detach(std::size_t expected_capacity)
184 {
185 if (inner->refcount == 1 && expected_capacity <= inner->capacity) {
186 return;
187 }
188 auto new_array = SharedVector::with_capacity(capacity: expected_capacity);
189 auto old_data = reinterpret_cast<const T *>(inner + 1);
190 auto new_data = reinterpret_cast<T *>(new_array.inner + 1);
191 for (std::size_t i = 0; i < inner->size; ++i) {
192 new (new_data + i) T(old_data[i]);
193 new_array.inner->size++;
194 }
195 *this = std::move(new_array);
196 }
197
198 void drop()
199 {
200 if (inner->refcount > 0 && (--inner->refcount) == 0) {
201 auto b = cbegin(), e = cend();
202 for (auto it = b; it < e; ++it) {
203 it->~T();
204 }
205 cbindgen_private::slint_shared_vector_free(ptr: reinterpret_cast<uint8_t *>(inner),
206 size: sizeof(SharedVectorHeader)
207 + inner->capacity * sizeof(T),
208 align: alignof(SharedVectorHeader));
209 }
210 }
211
212 static SharedVector with_capacity(std::size_t capacity)
213 {
214 auto mem = cbindgen_private::slint_shared_vector_allocate(
215 size: sizeof(SharedVectorHeader) + capacity * sizeof(T), align: alignof(SharedVectorHeader));
216 return SharedVector(new (mem) SharedVectorHeader { { 1 }, 0, capacity });
217 }
218
219#if !defined(DOXYGEN)
220 // Unfortunately, this cannot be generated by cbindgen because std::atomic is not understood
221 struct SharedVectorHeader
222 {
223 std::atomic<std::intptr_t> refcount;
224 std::size_t size;
225 std::size_t capacity;
226 };
227 static_assert(alignof(T) <= alignof(SharedVectorHeader),
228 "Not yet supported because we would need to add padding");
229 SharedVectorHeader *inner;
230 explicit SharedVector(SharedVectorHeader *inner) : inner(inner) { }
231#endif
232};
233
234#if !defined(DOXYGEN) // Hide these from Doxygen as Slice is private API
235template<typename T>
236bool operator==(cbindgen_private::Slice<T> a, cbindgen_private::Slice<T> b)
237{
238 if (a.len != b.len)
239 return false;
240 return std::equal(a.ptr, a.ptr + a.len, b.ptr);
241}
242template<typename T>
243bool operator!=(cbindgen_private::Slice<T> a, cbindgen_private::Slice<T> b)
244{
245 return !(a != b);
246}
247#endif // !defined(DOXYGEN)
248
249}
250

source code of slint/api/cpp/include/slint_sharedvector.h