Flow-IPC 1.0.0
Flow-IPC project: Full implementation reference.
stateless_allocator.hpp
Go to the documentation of this file.
1/* Flow-IPC: Shared Memory
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22
23namespace ipc::shm::stl
24{
25
26// Types.
27
28/**
29 * Stateless allocator usable with STL-compliant containers to store (or merely read) them directly in SHM in
30 * a given SHM-aware `Arena`. Please read background in shm::stl namespace doc header (shm_stl_fwd.hpp).
31 *
32 * ### How to use ###
33 * Suppose `T` is a container type with `Allocator` template param given as `Stateless_allocator<E>`, as well
34 * as any nested element types that are also containers also specifying `Allocator` as `Stateless_allocator<...>`.
35 * Suppose you want to work with a `T* t`, such that `t` points to `sizeof(T)` bytes in SHM inside some
36 * #Arena_obj `A`. In order to perform any work on the `*t` -- including construction, destruction, and any other
37 * operations that require internal alloc/dealloc/ptr-deref also inside `A` -- activate `&A` in the relevant
38 * thread using an Arena_activator. For example:
39 *
40 * ~~~
41 * using Shm_classic_arena = classic::Pool_arena;
42 * template<typename T>
43 * using Shm_classic_allocator = Stateless_allocator<T, Shm_classic_arena>;
44 * using Shm_classic_arena_activator = Arena_activator<Shm_classic_arena>;
45 *
46 * struct Widget { m_float m_a; m_int m_b; }
47 * using Widget_list = list<Widget, Shm_classic_allocator<Widget>>;
48 * using Widget_list_vec = vector<Widget_list, Shm_classic_allocator<Widget_list>>;
49 *
50 * Shm_classic_arena a(...); // Let `a` be a SHM-classic arena storing *x among other things.
51 *
52 * Widget_list_vec* x = // ...obtain pointer x to SHM-stored space for the Widget_list_vec in question.
53 * {
54 * Shm_classic_arena_activator arena_ctx(&a);
55 * // call x-> ctors, dtor, methods, operators....
56 * } // Return the active arena to previous value.
57 * ~~~
58 *
59 * Note you may nest Arena_activator contexts in stack-like fashion. Note, also, that `Arena_activator<A1>`
60 * operates completely independently from `Arena_activator<A2>`, even in the same thread, if `A1` is not same-as
61 * `A2`. So any "clash" would only occur (1) in a given thread, not across threads; (2) for a given SHM provider
62 * type, not across 2+ such types.
63 *
64 * The allocator is *stateless*. It takes no space within any container that uses it; and it is always default-cted
65 * by the container code itself -- never by the user. That is the reason Arena_activator is necessary (when
66 * allocating/deallocating).
67 *
68 * ### `Arena` type requirements (for full capabilities) ###
69 * For write+allocate+deallocate capabilities of Stateless_allocator: an `Arena` must have the following members:
70 * - `void* allocate(size_t n);`: Allocate uninitialized buffer of `n` bytes in this SHM arena;
71 * return locally-dereferenceable pointer to that buffer. Throw exception if ran out of resources.
72 * (Some providers try hard to avoid this.)
73 * - `void deallocate(void* p)`: Undo `allocate()` that returned `p`; or the equivalent operation
74 * if the SHM provider allows process 2 to deallocate something that was allocated by process 1 (and `p` indeed
75 * was `allocate()`ed in a different process but transmitted to the current process; and was properly made
76 * locally-dereferenceable before passing it to this method).
77 * - `template<typename T> class Pointer`: `Pointer<T>` must, informally speaking, mimic `T*`; formally
78 * being a *fancy pointer* in the STL sense. The locally-dereferenceable `T*` it yields must point to the
79 * same underlying area in SHM regardless of in which `Arena`-aware process the deref API is invoked.
80 * - The `class` keyword is for exposition only; it can also be `using`, `typedef`, or anything else,
81 * provided it mimics `T*` as described.
82 * - For example, if #Arena_obj is classic::Pool_arena, then classic::Pool_arena::Pointer might be
83 * `bipc::offset_ptr`, which internally stores merely an offset within the same SHM-pool versus its own
84 * `this`; this works great as long indeed the `Pointer` *itself* is located inside the SHM-pool as the
85 * thing to which it points.
86 *
87 * @todo Currently `Arena::Pointer` shall be a fancy-pointer, but we could support raw pointers also. Suppose
88 * #Arena_obj is set up in such a way as to map all processes' locally-dereferenceable pointers to the same SHM
89 * location to the same numeric value (by specifying each pool's start as some predetermined numerical value in
90 * the huge 64-bit vaddr space -- in all processes sharing that SHM pool. Now no address translation is
91 * needed, and `Arena::Pointer` could be simply `T*`. As of this writing some inner impl details suppose
92 * it being a fancy-pointer, and it's the only active need we have; but that could be tweaked with a bit of effort.
93 *
94 * ### Use in read-only borrowing mode ###
95 * Taken as an example, SHM-classic (shm::classic::Pool_arena) `Arena`s can always both read and write, allocate and
96 * deallocate. By contrast, shm::arena_lend has allocate/write-capable `Arena` and, on the borrower side,
97 * read-only borrower-quasi-`Arena`. If one needs to merely *read* a SHM-stored STL-compliant structure with such
98 * an `Arena`, then **only the `Pointer` type requirement above applies**.
99 * - `allocate()` and `deallocate()` shall not be used by (nor need to be present at compile-time for)
100 * Stateless_allocator.
101 * - No activator -- in fact, no `Arena` *object* -- only the *type*! -- shall be used by Stateless_allocator.
102 *
103 * @internal
104 * ### Implementation ###
105 * It is self-explanatory; the trick was knowing what was actually required according to STL-compliance documentation.
106 * The great thing is, since C++11, only very few things are indeed needed in our situation; the rest is
107 * supplied with sensible default by `allocator_traits` which is how STL-compliant container code actually accesses
108 * `Allocator`s like ours.
109 *
110 * @endinternal
111 *
112 * @tparam T
113 * Pointed-to type for the allocator. See standard C++ `Allocator` concept.
114 * @tparam Arena
115 * See above.
116 */
117template<typename T, typename Arena>
119{
120public:
121 // Types.
122
123 /// Short-hand for `T`.
124 using Value = T;
125
126 /// Short-hand for the `Arena` type this uses for allocation/deallocation/pointer semantics.
127 using Arena_obj = Arena;
128
129 /// The required pointer-like type. See also #pointer.
130 using Pointer = typename Arena_obj::template Pointer<Value>;
131
132 /// Alias to #Pointer for compatibility with STL-compliant machinery (traits, etc.).
134
135 /// Alias to #Value for compatibility with STL-compliant machinery (traits, etc.).
137
138 // Constructors/destructor.
139
140 /// Allocator concept requirement for default-ctor: no-op since it's a stateless allocator.
142
143 /**
144 * Allocator concept requirement for copy-ctor from allocator-to-another-type: no-op since it's a stateless
145 * allocator.
146 *
147 * @tparam U
148 * Like #Value.
149 * @param src_ignored
150 * The other allocator.
151 */
152 template<typename U>
153 explicit Stateless_allocator(const Stateless_allocator<U, Arena>& src_ignored);
154
155 /**
156 * Allocator concept requirement for move-ctor from allocator-to-another-type: no-op since it's a stateless
157 * allocator.
158 *
159 * @tparam U
160 * Like #Value.
161 * @param src_ignored
162 * The other allocator.
163 */
164 template<typename U>
166
167 // Methods.
168
169 /**
170 * Allocates an uninitialized buffer of given size, or throws exception if `Arena_obj::allocate()` does;
171 * satisfies formal requirements of STL-compliant `Allocator` concept. See cppreference.com for those formal
172 * requirements.
173 *
174 * @param n
175 * The buffer allocated shall be `n * sizeof(Value)`. Note: This is a #Value count; not a byte count.
176 * @return Locally-dereferenceable pointer to the SHM-allocated buffer.
177 * The buffer is *not* initialized. E.g., depending on the nature of `T` you may want to placement-ct it
178 * at this address subsequently.
179 */
180 Pointer allocate(size_t n) const;
181
182 /**
183 * Deallocates buffer in SHM previously allocated via allocate() in this or other (if #Arena_obj supports this)
184 * process, as long as `p` refers to the beginning of the buffer returned by that allocate();
185 * satisfies formal requirement of STL-compliant `Allocator` concept. See cppreference.com for those formal
186 * requirements. Does not throw (as required).
187 *
188 * @param p
189 * See above.
190 * @param n_ignored
191 * Ignored.
192 */
193 void deallocate(Pointer p, size_t n_ignored = 0) const noexcept;
194}; // class Stateless_allocator
195
196// Free functions: in *_fwd.hpp.
197
198// Template implementations.
199
200template<typename T, typename Arena>
201typename Stateless_allocator<T, Arena>::Pointer Stateless_allocator<T, Arena>::allocate(size_t n) const
202{
204 assert(arena && "Before working with SHM-stored STL-compliant objects: activate an Arena via Arena_activator "
205 "in the thread in question.");
206
207 /* void* -> Value* -> Arena::Pointer<Value>. The last -> is a key, non-trivial operation that creates
208 * the SHM-storable fancy-pointer from a locally-dereferenceable raw pointer. Though typically the fancy-pointer
209 * template Arena::Pointer<> would have a ctor that takes a Value*, officially in STL-compliant land it's
210 * the static pointer_to() factory. pointer_traits<>::pointer_to(T&) does that for non-raw
211 * pointers per cppreference.com and also yields a simple &x for raw pointer types (also correct). */
212 return std::pointer_traits<Pointer>::pointer_to
213 (*(static_cast<Value*>
214 (arena->allocate(n * sizeof(Value))))); // May throw.
215}
216
217template<typename T, typename Arena>
219{
221 assert(arena && "Before working with SHM-stored STL-compliant objects: activate an Arena via Arena_activator "
222 "in the thread in question.");
223
224 /* Arena::Pointer<Value> -> Value* -> void*. The first -> is a key, non-trivial operation that obtains
225 * a locally-dereferenceable raw pointer from the fancy-pointer. This is expressible generically in a number
226 * of ways; but for any fancy-pointer type `.operator->()` will do it.
227 * @todo In C++20 std::to_address() would invoke that for fancy-pointer and simply pass-through the T*
228 * for raw pointer. If we wanted to allow raw pointer support then we'd have to either wait for C++20 and
229 * use that helper; or implement it in C++17 ourselves (see cppreference.com for inspiration). */
230 arena->deallocate(static_cast<void*>(p.operator->()));
231}
232
233template<typename T, typename Arena>
235
236template<typename T, typename Arena>
237template<typename U>
239{
240 // As usual... do nothin'... there's no state.
241}
242
243template<typename T, typename Arena>
244template<typename U>
246{
247 // As usual... do nothin'... there's no state.
248}
249
250template<typename Arena, typename T1, typename T2>
252{
253 static_assert(std::is_empty_v<Stateless_allocator<T1, Arena>>,
254 "Stateless_allocator<> is currently designed around being empty (static-data-only) -- "
255 "did it gain state?");
256 return true;
257}
258
259template<typename Arena, typename T1, typename T2>
261{
262 return false;
263}
264
265} // namespace ipc::shm::stl
static Arena_obj * this_thread_active_arena()
Returns the active arena, as understood by Stateless_allocator<Arena_obj> at this point in the callin...
Stateless allocator usable with STL-compliant containers to store (or merely read) them directly in S...
Stateless_allocator()
Allocator concept requirement for default-ctor: no-op since it's a stateless allocator.
bool operator==(const Stateless_allocator< T1, Arena > &val1, const Stateless_allocator< T2, Arena > &val2)
Returns true for any 2 Stateless_allocators managing the same Stateless_allocator::Arena_obj.
bool operator!=(const Stateless_allocator< T1, Arena > &val1, const Stateless_allocator< T2, Arena > &val2)
Returns false for any 2 Stateless_allocators managing the same Stateless_allocator::Arena_obj.
typename Arena_obj::template Pointer< Value > Pointer
The required pointer-like type. See also pointer.
Pointer allocate(size_t n) const
Allocates an uninitialized buffer of given size, or throws exception if Arena_obj::allocate() does; s...
Arena Arena_obj
Short-hand for the Arena type this uses for allocation/deallocation/pointer semantics.
void deallocate(Pointer p, size_t n_ignored=0) const noexcept
Deallocates buffer in SHM previously allocated via allocate() in this or other (if Arena_obj supports...
Pointer pointer
Alias to Pointer for compatibility with STL-compliant machinery (traits, etc.).
Value value_type
Alias to Value for compatibility with STL-compliant machinery (traits, etc.).
ipc::shm sub-module providing integration between STL-compliant components (including containers) and...