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