Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
InlineBuffer.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Memory_InlineBuffer_h_
5#define _Stroika_Foundation_Memory_InlineBuffer_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <span>
10
11#include "Stroika/Foundation/Common/Common.h"
12#include "Stroika/Foundation/Common/Concepts.h"
14#include "Stroika/Foundation/Memory/Common.h"
15
16/**
17 * \file
18 *
19 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
20 */
21
22namespace Stroika::Foundation::Memory {
23
24 namespace Support::InlineBuffer {
25
26 /**
27 */
28 template <typename T = byte>
29 constexpr size_t DefaultInlineSize ()
30 {
31 // note must be defined here, not in inl file, due to use as default template argument
32 auto r = ((4096 / sizeof (T)) == 0 ? 1 : (4096 / sizeof (T)));
33 Ensure (r >= 1);
34 return r;
35 }
36
37 }
38
39 /**
40 * \brief Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed automatically switches to heap based so can grow - BUF_SIZE is number of T elements allocated inline
41 *
42 * Typically, InlineBuffer<> combines the performance of using a pre-allocated fixed-sized buffer to store arrays with
43 * the safety and flexibility of using the free store (malloc).
44 *
45 * Think of it as a hybrid between std::vector<> and std::array - with functionality like
46 * std::vector, but performance more like std::array.
47 *
48 * \note if BUF_SIZE is zero, this class behaves much like 'vector<T>'
49 *
50 * Internally, InlineBuffer maintains a fixed buffer (of size given by its BUFFER_SIZE template parameter)
51 * and it uses that while things fit, and switches to using a free-store-allocated data as needed. Pick the BUF_SIZE
52 * wisely, and you always end up with fixed sized objects. Pick poorly, and it will still work, but allocating the
53 * data on the free store.
54 *
55 * typically sizeof(InlineBuffer<T,BUF_SIZE>) will come to roughly BUF_SIZE*sizeof(T).
56 *
57 * \see also StackBuffer<T,BUF_SIZE> - is a type alias for InlineBuffer, but with sizes tuned
58 * for stack based usage. Prefer StackBuffer for a scratch buffer local to a function.
59 *
60 * All allocated objects are default initialized, unless they are allocated through a call to resize_uninitialized(), or
61 * the constructor with the argument eUninitialized
62 *
63 * \note Until Stroika 2.1r4, this class was called SmallStackBuffer<>, cuz that's basically the most common
64 * scenario it was used for up til that point.
65 *
66 * \par Example Usage
67 * @see Samples/SimpleService project
68 * \code
69 * Memory::InlineBuffer<byte> useKey{keyLen}; // no need to default initialize cuz done automatically
70 * (void)::memcpy (useKey.begin (), key.begin (), min (keyLen, key.size ()));
71 * \endcode
72 * OR
73 * \code
74 * Memory::InlineBuffer<byte> useKey{Memory::eUninitialized, keyLen};
75 * (void)::memset (useKey.begin (), 0, keyLen);
76 * (void)::memcpy (useKey.begin (), key.begin (), min (keyLen, key.size ()));
77 * \endcode
78 *
79 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
80 *
81 * \note Satisfies Concepts:
82 * o Common::explicitly_convertible_to<InlineBuffer<byte>, span<const byte>>
83 *
84 * \note InlineBuffer<T> can roughly be used as a replacement for vector<> - behaving similarly, except that its optimized
85 * for the case where the caller statically knows GENERALLY the right size for the buffer, in which case it can be
86 * allocated more cheaply.
87 *
88 * InlineBuffer<T> CAN be copied, and will properly construct/destruct array members as they are added/removed.
89 * (new feature as of Stroika v2.1b6).
90 *
91 * \note We do not provide an operator[] overload because this creates ambiguity with the operator* overload.
92 *
93 * \note Implementation Note - we store the 'capacity' in a union (fCapacityOfFreeStoreAllocation_) overlapping with fInlinePreallocatedBuffer_ if its > BUF_SIZE, and pin it at the minimum
94 * to BUF_SIZE
95 */
96 template <typename T = byte, size_t BUF_SIZE = Support::InlineBuffer::DefaultInlineSize<T> ()>
97 class InlineBuffer final {
98 public:
99 /**
100 */
101 using value_type = T;
102
103 public:
104 /**
105 */
106 using pointer = T*;
107 using const_pointer = const T*;
108
109 public:
110 /**
111 */
112 using iterator = T*;
113 using const_iterator = const T*;
114
115 public:
116 /**
117 */
118 using reference = T&;
119 using const_reference = const T&;
120
121 public:
122 /**
123 * kMinCapacity is the baked in minimum capacity of this buffer (the inline allocated part).
124 */
125 static constexpr size_t kMinCapacity = BUF_SIZE;
126
127 public:
128 /**
129 * InlineBuffer::InlineBuffer (size_t) specifies the initial size - like InlineBuffer::InlineBuffer {} followed by resize (n);
130 * InlineBuffer::default-ctor creates a zero-sized stack buffer (so resize with resize, or push_back etc).
131 */
132 InlineBuffer () noexcept;
133 InlineBuffer (size_t nElements)
134 requires (default_initializable<T>);
135 InlineBuffer (size_t nElements, Common::ArgByValueType<T> fillValue);
136 InlineBuffer (UninitializedConstructorFlag flag, size_t nElements);
137 template <size_t FROM_BUF_SIZE>
138 InlineBuffer (const InlineBuffer<T, FROM_BUF_SIZE>& src);
139 InlineBuffer (const InlineBuffer& src);
141 template <input_iterator ITERATOR_OF_T, sentinel_for<remove_cvref_t<ITERATOR_OF_T>> ITERATOR_OF_T2>
142 InlineBuffer (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end);
143 template <ISpanOfT<T> SPAN_T>
144 InlineBuffer (const SPAN_T& copyFrom);
145 ~InlineBuffer ();
146
147 public:
148 /**
149 */
150 nonvirtual InlineBuffer& operator= (const InlineBuffer& rhs);
151 nonvirtual InlineBuffer& operator= (InlineBuffer&& rhs);
152 template <ISpanOfT<T> SPAN_T>
153 nonvirtual InlineBuffer& operator= (const SPAN_T& copyFrom);
154
155 public:
156 /**
157 * \brief returns the same value as data () - a live pointer to the start of the buffer.
158 *
159 * \note This was changed from non-explicit to explicit in Stroika v3.0d1
160 */
161 nonvirtual explicit operator const T*() const noexcept;
162 nonvirtual explicit operator T*() noexcept;
163
164 public:
165 /**
166 * \brief returns a (possibly const) pointer to the start of the live buffer data. This return value can be invalidated
167 * by any changes in size/capacity of the InlineBuffer (but not by other changes, like at).
168 */
169 nonvirtual pointer data () noexcept;
170 nonvirtual const_pointer data () const noexcept;
171
172 public:
173 /**
174 */
175 nonvirtual iterator begin () noexcept;
176 nonvirtual const_iterator begin () const noexcept;
177
178 public:
179 /**
180 */
181 nonvirtual iterator end () noexcept;
182 nonvirtual const_iterator end () const noexcept;
183
184 public:
185 /**
186 * \pre i < size ()
187 */
188 nonvirtual reference at (size_t i) noexcept;
189 nonvirtual const_reference at (size_t i) const noexcept;
190
191 public:
192 /**
193 * \pre i < size ()
194 */
195 nonvirtual reference operator[] (size_t i) noexcept;
196 nonvirtual const_reference operator[] (size_t i) const noexcept;
197
198 public:
199 /**
200 * Returns the 'size' the InlineBuffer can be resized up to without any additional memory allocations.
201 * This always returns a value at least as large as the BUF_SIZE template parameter.
202 *
203 * @see reserve
204 */
205 constexpr size_t capacity () const noexcept;
206
207 public:
208 /**
209 * Provide a hint as to how much (contiguous) space to reserve.
210 *
211 * if (default true) atLeast flag is true, newCapacity is adjusted (increased) with GetScaledUpCapacity
212 * to minimize needless copies as buffer grows, but only if any memory allocation would have been needed anyhow.
213 *
214 * if atLeast is false, then reserve sets the capacity to exactly the amount prescribed (unless its less than BUF_SIZE).
215 * This can be used to free-up used memory.
216 *
217 * @see capacity
218 *
219 * \pre (newCapacity >= size ());
220 * \post (newCapacity <= BUF_SIZE and capacity () == BUF_SIZE) or (newCapacity > BUF_SIZE and newCapacity == capacity ());
221 */
222 nonvirtual void reserve (size_t newCapacity, bool atLeast = true);
223
224 public:
225 [[deprecated ("Since Stroika v3.0d1, just use reserve with atLeast flag=true)")]] void ReserveAtLeast (size_t newCapacityAtLeast)
226 {
227 reserve (newCapacityAtLeast, true);
228 }
229
230 public:
231 /**
232 * Returns the number of (constructed) elements in the buffer in ELEMENTS (not necessarily in bytes).
233 *
234 * \post GetSize () <= capacity ();
235 */
236 nonvirtual size_t GetSize () const noexcept;
237
238 public:
239 /**
240 * Returns the number of (constructed) elements in the buffer.
241 *
242 * @see GetSize (); // alias
243 * @see capacity ();
244 *
245 * \post size () <= capacity ();
246 */
247 nonvirtual size_t size () const noexcept;
248
249 public:
250 /**
251 * returns true iff size () == 0
252 */
253 nonvirtual bool empty () const noexcept;
254
255 public:
256 /**
257 * \brief Grow or shrink the buffer. The 'size' is the number of constructed elements, and this function automatically
258 * assures the capacity is maintained at least as large as the size. Overload without fillValue requires default_initializable<T>.
259 *
260 * If resize () causes the list to grow, the new elements are fillValue/default-initialized()
261 *
262 * \post GetSize () <= capacity ();
263 *
264 * \note Shrinking constructs nothing, so resize (n) does not build a fillValue in that case.
265 *
266 * \post GetSize () <= capacity ();
267 */
268 nonvirtual void resize (size_t nElements)
269 requires (default_initializable<T>);
270 nonvirtual void resize (size_t nElements, Common::ArgByValueType<T> fillValue);
271
272 public:
273 /**
274 * \brief same as resize (), except leaves newly created elements uninitialized (requires is_trivially_copyable_v<T>)
275 *
276 * \pre is_trivially_copyable_v<T>
277 * \post GetSize () <= capacity ();
278 */
279 nonvirtual void resize_uninitialized (size_t nElements)
280 requires (is_trivially_copyable_v<T> and is_trivially_destructible_v<T>);
281
282 public:
283 /**
284 * Same as resize (nElements), except asserts (documents) the new size must be smaller or equal to the old size.
285 *
286 * \pre nElements <= size ()
287 */
288 nonvirtual void ShrinkTo (size_t nElements);
289
290 public:
291 /**
292 * Grow the buffer to at least nElements in size (wont shrink). The 'size' is the number of constructed elements,
293 * and this function automatically assures the capacity is maintained at least as large as the size.
294 *
295 * \post GetSize () <= capacity ();
296 */
297 nonvirtual void GrowToSize (size_t nElements)
298 requires (default_initializable<T>);
299
300 public:
301 /**
302 * \brief same as GrowToSize (), except you say what to fill new elements with - so it works for any
303 * copy-constructible T. \see resize (size_t, Common::ArgByValueType<T>)
304 */
305 nonvirtual void GrowToSize (size_t nElements, Common::ArgByValueType<T> fillValue);
306
307 public:
308 /**
309 * \brief same as GrowToSize (), except leaves newly created elements uninitialized (requires is_trivially_copyable_v<T>)
310 *
311 * \pre is_trivially_copyable_v<T>
312 * \post GetSize () <= capacity ();
313 */
314 nonvirtual void GrowToSize_uninitialized (size_t nElements)
315 requires (is_trivially_copyable_v<T>);
316
317 public:
318 /**
319 */
320 template <ISpanOfT<T> SPAN_T>
321 nonvirtual void Insert (size_t at, const SPAN_T& copyFrom);
322 nonvirtual void Insert (size_t at, const T& item);
323
324 public:
325 /**
326 * mimic the std::vector::insert () API - but better to call Insert ()
327 */
328 nonvirtual void insert (iterator i, const_pointer from, const_pointer to);
329
330 public:
331 /**
332 * With a single T argument, this is somewhat STLISH, but also takes overload of a span, so you can append multiple.
333 *
334 * @aliases Append
335 *
336 * \see also push_back_coerced ()
337 */
338 nonvirtual void push_back (Common::ArgByValueType<T> e);
339 template <ISpanOfT<T> SPAN_T>
340 nonvirtual void push_back (const SPAN_T& copyFrom);
341
342 public:
343 /**
344 * \brief same as push_back (span{}) except that the span type doesn't need to match exactly, so long as indirected items can be copied to destination (with static_cast).
345 */
346 template <ISpan SPAN_T>
347 nonvirtual void push_back_coerced (const SPAN_T& copyFrom);
348
349 public:
350 /**
351 * This doesn't change InlineBuffer::capacity, but just shuffles (and destroys) - remote (to-from) items starting at to
352 *
353 * \req from <= to (if ==, does nothing)
354 * \req to <= size ()
355 *
356 * Remove (i) same as Remove (i, i+1);
357 */
358 nonvirtual void Remove (size_t at);
359 nonvirtual void Remove (size_t from, size_t to);
360
361 public:
362 /**
363 */
364 nonvirtual void clear () noexcept;
365
366#if qStroika_Foundation_Debug_AssertionsChecked
367 private:
368 static constexpr byte kGuard1_[8] = {
369 0x45_b, 0x23_b, 0x12_b, 0x56_b, 0x99_b, 0x76_b, 0x12_b, 0x55_b,
370 };
371 static constexpr byte kGuard2_[8] = {
372 0x15_b, 0x32_b, 0xa5_b, 0x16_b, 0x11_b, 0x7a_b, 0x90_b, 0x10_b,
373 };
374#endif
375
376 private:
377 // note must be inline declared here since used in type definition below
378 static constexpr size_t SizeInBytes_ (size_t nElts) noexcept
379 {
380 if (nElts == 0) {
381 return 1; // avoid syntax error due to zero sized array
382 }
383 return sizeof (T[1]) * nElts; // not sure why return sizeof (T[nElts]); fails on vs2k21?
384 }
385
386 private:
387 nonvirtual byte* LiveDataAsAllocatedBytes_ () noexcept;
388
389 private:
390 static byte* Allocate_ (size_t bytes);
391
392 private:
393 static void Deallocate_ (byte* bytes) noexcept;
394
395 private:
396 static byte* Reallocate_ (byte* bytes, size_t n)
397 requires (is_trivially_copyable_v<T>);
398
399 private:
400 size_t fSize_{};
401#if qStroika_Foundation_Debug_AssertionsChecked
402 byte fGuard1_[sizeof (kGuard1_)];
403#endif
404 DISABLE_COMPILER_MSC_WARNING_START (4324)
405 union {
406 size_t fCapacityOfFreeStoreAllocation_; // only valid if fLiveData_ != &fInlinePreallocatedBuffer_[0]
407 alignas (T) byte fInlinePreallocatedBuffer_[SizeInBytes_ (BUF_SIZE)]; // alignas both since sometimes accessed as array of T, and sometimes as size_t
408 };
409 DISABLE_COMPILER_MSC_WARNING_END (4324)
410
411#if qStroika_Foundation_Debug_AssertionsChecked
412 byte fGuard2_[sizeof (kGuard2_)];
413#endif
414 T* fLiveData_{};
415
416 private:
417 // generally unneeded optimization, but allows quick check of sz against just BUF_SIZE in most cases
418 constexpr bool HasEnoughCapacity_ (size_t sz) const
419 {
420 // Computing capacity - while simple and quick, is much slower than this check which
421 // is nearly always sufficient. So a slight performance tweak
422 if (sz <= BUF_SIZE) [[likely]] {
423 return true;
424 }
425 return sz <= capacity ();
426 }
427
428 private:
429 constexpr bool UsingInlinePreallocatedBuffer_ () const noexcept;
430
431 public:
432 nonvirtual void Invariant () const noexcept;
433
434 private:
435#if qStroika_Foundation_Debug_AssertionsChecked
436 nonvirtual void Invariant_ () const noexcept;
437 nonvirtual void ValidateGuards_ () const noexcept;
438#endif
439
440 private:
441 constexpr T* BufferAsT_ () noexcept;
442 constexpr const T* BufferAsT_ () const noexcept;
443
444 private:
445 static void DestroyElts_ (T* start, T* end) noexcept;
446 };
447 static_assert (Common::explicitly_convertible_to<InlineBuffer<byte>, span<const byte>>);
448
449}
450
451/*
452 ********************************************************************************
453 ***************************** Implementation Details ***************************
454 ********************************************************************************
455 */
456#include "InlineBuffer.inl"
457
458#endif /*_Stroika_Foundation_Memory_InlineBuffer_h_*/
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual void GrowToSize_uninitialized(size_t nElements)
same as GrowToSize (), except leaves newly created elements uninitialized (requires is_trivially_copy...
nonvirtual pointer data() noexcept
returns a (possibly const) pointer to the start of the live buffer data. This return value can be inv...
nonvirtual void push_back(Common::ArgByValueType< T > e)
constexpr size_t capacity() const noexcept
nonvirtual size_t size() const noexcept
nonvirtual reference at(size_t i) noexcept
nonvirtual void push_back_coerced(const SPAN_T &copyFrom)
same as push_back (span{}) except that the span type doesn't need to match exactly,...
nonvirtual void GrowToSize(size_t nElements)
nonvirtual void reserve(size_t newCapacity, bool atLeast=true)
nonvirtual void resize(size_t nElements)
Grow or shrink the buffer. The 'size' is the number of constructed elements, and this function automa...
nonvirtual void ShrinkTo(size_t nElements)
nonvirtual size_t GetSize() const noexcept
nonvirtual void insert(iterator i, const_pointer from, const_pointer to)
nonvirtual bool empty() const noexcept
nonvirtual void resize_uninitialized(size_t nElements)
same as resize (), except leaves newly created elements uninitialized (requires is_trivially_copyable...
use ISpanOfT<T> as a concept declaration for parameters where you want a span, but accept either T or...