Stroika Library 3.0d16
 
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 InlineBuffer<T> can roughly be used as a replacement for vector<> - behaving similarly, except that its optimized
77 * for the case where the caller statically knows GENERALLY the right size for the buffer, in which case it can be
78 * allocated more cheaply.
79 *
80 * InlineBuffer<T> CAN be copied, and will properly construct/destruct array members as they are added/removed.
81 * (new feature as of Stroika v2.1b6).
82 *
83 * \note We do not provide an operator[] overload because this creates ambiguity with the operator* overload.
84 *
85 * \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
86 * to BUF_SIZE
87 *
88 */
89 template <typename T = byte, size_t BUF_SIZE = InlineBuffer_DefaultInlineSize<T> ()>
91 public:
92 /**
93 */
94 using value_type = T;
95
96 public:
97 /**
98 */
99 using pointer = T*;
100 using const_pointer = const T*;
101
102 public:
103 /**
104 */
105 using iterator = T*;
106 using const_iterator = const T*;
107
108 public:
109 /**
110 */
111 using reference = T&;
112 using const_reference = const T&;
113
114 public:
115 /**
116 * kMinCapacity is the baked in minimum capacity of this buff (inline allocated part).
117 */
118 static constexpr size_t kMinCapacity = BUF_SIZE;
119
120 public:
121 /**
122 * InlineBuffer::InlineBuffer (size_t) specifies the initial size - like InlineBuffer::InlineBuffer {} followed by resize (n);
123 * InlineBuffer::default-ctor creates a zero-sized stack buffer (so resize with resize, or push_back etc).
124 */
125 InlineBuffer () noexcept;
126 InlineBuffer (size_t nElements);
127 InlineBuffer (UninitializedConstructorFlag flag, size_t nElements);
128 template <size_t FROM_BUF_SIZE>
129 InlineBuffer (const InlineBuffer<T, FROM_BUF_SIZE>& src);
130 InlineBuffer (const InlineBuffer& src);
132 template <input_iterator ITERATOR_OF_T, sentinel_for<remove_cvref_t<ITERATOR_OF_T>> ITERATOR_OF_T2>
133 InlineBuffer (const ITERATOR_OF_T& start, ITERATOR_OF_T2&& end);
134 template <ISpanOfT<T> SPAN_T>
135 InlineBuffer (const SPAN_T& copyFrom);
136 ~InlineBuffer ();
137
138 public:
139 /**
140 */
141 nonvirtual InlineBuffer& operator= (const InlineBuffer& rhs);
142 nonvirtual InlineBuffer& operator= (InlineBuffer&& rhs);
143 template <ISpanOfT<T> SPAN_T>
144 nonvirtual InlineBuffer& operator= (const SPAN_T& copyFrom);
145
146 public:
147 /**
148 * \brief returns the same value as data () - a live pointer to the start of the buffer.
149 *
150 * \note This was changed from non-explicit to explicit in Stroika v3.0d1
151 */
152 nonvirtual explicit operator const T* () const noexcept;
153 nonvirtual explicit operator T* () noexcept;
154
155 public:
156 /**
157 * \brief returns a (possibly const) pointer to the start of the live buffer data. This return value can be invalidated
158 * by any changes in size/capacity of the InlineBuffer (but not by other changes, like at).
159 */
160 nonvirtual pointer data () noexcept;
161 nonvirtual const_pointer data () const noexcept;
162
163 public:
164 /**
165 */
166 nonvirtual iterator begin () noexcept;
167 nonvirtual const_iterator begin () const noexcept;
168
169 public:
170 /**
171 */
172 nonvirtual iterator end () noexcept;
173 nonvirtual const_iterator end () const noexcept;
174
175 public:
176 /**
177 * \pre i < size ()
178 */
179 nonvirtual reference at (size_t i) noexcept;
180 nonvirtual const_reference at (size_t i) const noexcept;
181
182 public:
183 /**
184 * \pre i < size ()
185 */
186 nonvirtual reference operator[] (size_t i) noexcept;
187 nonvirtual const_reference operator[] (size_t i) const noexcept;
188
189 public:
190 /**
191 * Returns the 'size' the InlineBuffer can be resized up to without any additional memory allocations.
192 * This always returns a value at least as large as the BUF_SIZE template parameter.
193 *
194 * @see reserve
195 */
196 constexpr size_t capacity () const noexcept;
197
198 public:
199 /**
200 * Provide a hint as to how much (contiguous) space to reserve.
201 *
202 * if (default true) atLeast flag is true, newCapacity is adjusted (increased) with GetScaledUpCapacity
203 * to minimize needless copies as buffer grows, but only if any memory allocation would have been needed anyhow.
204 *
205 * if atLeast is false, then reserve sets the capacity to exactly the amount prescribed (unless its less than BUF_SIZE).
206 * This can be used to free-up used memory.
207 *
208 * @see capacity
209 *
210 * \pre (newCapacity >= size ());
211 * \post (newCapacity <= BUF_SIZE and capacity () == BUF_SIZE) or (newCapacity > BUF_SIZE and newCapacity == capacity ());
212 */
213 nonvirtual void reserve (size_t newCapacity, bool atLeast = true);
214
215 public:
216 [[deprecated ("Since Stroika v3.0d1, just use reserve with atLeast flag=true)")]] void ReserveAtLeast (size_t newCapacityAtLeast)
217 {
218 reserve (newCapacityAtLeast, true);
219 }
220
221 public:
222 /**
223 * Returns the number of (constructed) elements in the buffer in ELEMENTS (not necessarily in bytes).
224 *
225 * \post GetSize () <= capacity ();
226 */
227 nonvirtual size_t GetSize () const noexcept;
228
229 public:
230 /**
231 * Returns the number of (constructed) elements in the buffer.
232 *
233 * @see GetSize (); // alias
234 * @see capacity ();
235 *
236 * \post size () <= capacity ();
237 */
238 nonvirtual size_t size () const noexcept;
239
240 public:
241 /**
242 * returns true iff size () == 0
243 */
244 nonvirtual bool empty () const noexcept;
245
246 public:
247 /**
248 * Grow or shrink the buffer. The 'size' is the number of constructed elements, and this function automatically
249 * assures the capacity is maintained at least as large as the size.
250 *
251 * If resize () causes the list to grow, the new elements are default-initialized()
252 *
253 * \post GetSize () <= capacity ();
254 */
255 nonvirtual void resize (size_t nElements);
256
257 public:
258 /**
259 * \brief same as resize (), except leaves newly created elements uninitialized (requires is_trivially_copyable_v<T>)
260 *
261 * \pre is_trivially_copyable_v<T>
262 * \post GetSize () <= capacity ();
263 */
264 nonvirtual void resize_uninitialized (size_t nElements)
265 requires (is_trivially_copyable_v<T> and is_trivially_destructible_v<T>);
266
267 public:
268 /**
269 * Same as resize (nElements), except asserts (documents) the new size must be smaller or equal to the old size.
270 *
271 * \pre nElements <= size ()
272 */
273 nonvirtual void ShrinkTo (size_t nElements);
274
275 public:
276 /**
277 * Grow the buffer to at least nElements in size (wont shrink). The 'size' is the number of constructed elements,
278 * and this function automatically assures the capacity is maintained at least as large as the size.
279 *
280 * \post GetSize () <= capacity ();
281 */
282 nonvirtual void GrowToSize (size_t nElements);
283
284 public:
285 /**
286 * \brief same as GrowToSize (), except leaves newly created elements uninitialized (requires is_trivially_copyable_v<T>)
287 *
288 * \pre is_trivially_copyable_v<T>
289 * \post GetSize () <= capacity ();
290 */
291 nonvirtual void GrowToSize_uninitialized (size_t nElements)
292 requires (is_trivially_copyable_v<T>);
293
294 public:
295 /**
296 */
297 template <ISpanOfT<T> SPAN_T>
298 nonvirtual void Insert (size_t at, const SPAN_T& copyFrom);
299
300 public:
301 /**
302 * mimic the std::vector::insert () API - but better to call Insert ()
303 */
304 nonvirtual void insert (iterator i, const_pointer from, const_pointer to);
305
306 public:
307 /**
308 * With a single T argument, this is somewhat STLISH, but also takes overload of a span, so you can append multiple.
309 *
310 * @aliases Append
311 *
312 * \see also push_back_coerced ()
313 */
314 nonvirtual void push_back (Common::ArgByValueType<T> e);
315 template <ISpanOfT<T> SPAN_T>
316 nonvirtual void push_back (const SPAN_T& copyFrom);
317
318 public:
319 /**
320 * \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).
321 */
322 template <ISpan SPAN_T>
323 nonvirtual void push_back_coerced (const SPAN_T& copyFrom);
324
325 public:
326 /**
327 */
328 nonvirtual void clear () noexcept;
329
330#if qStroika_Foundation_Debug_AssertionsChecked
331 private:
332 static constexpr byte kGuard1_[8] = {
333 0x45_b, 0x23_b, 0x12_b, 0x56_b, 0x99_b, 0x76_b, 0x12_b, 0x55_b,
334 };
335 static constexpr byte kGuard2_[8] = {
336 0x15_b, 0x32_b, 0xa5_b, 0x16_b, 0x11_b, 0x7a_b, 0x90_b, 0x10_b,
337 };
338#endif
339
340 private:
341 // note must be inline declared here since used in type definition below
342 static constexpr size_t SizeInBytes_ (size_t nElts) noexcept
343 {
344 if (nElts == 0) {
345 return 1; // avoid syntax error due to zero sized array
346 }
347 return sizeof (T[1]) * nElts; // not sure why return sizeof (T[nElts]); fails on vs2k21?
348 }
349
350 private:
351 nonvirtual byte* LiveDataAsAllocatedBytes_ () noexcept;
352
353 private:
354 static byte* Allocate_ (size_t bytes);
355
356 private:
357 static void Deallocate_ (byte* bytes) noexcept;
358
359 private:
360 static byte* Reallocate_ (byte* bytes, size_t n)
361 requires (is_trivially_copyable_v<T>);
362
363 private:
364 size_t fSize_{};
365#if qStroika_Foundation_Debug_AssertionsChecked
366 byte fGuard1_[sizeof (kGuard1_)];
367#endif
368 DISABLE_COMPILER_MSC_WARNING_START (4324)
369 union {
370 size_t fCapacityOfFreeStoreAllocation_; // only valid if fLiveData_ != &fInlinePreallocatedBuffer_[0]
371 alignas (T) byte fInlinePreallocatedBuffer_[SizeInBytes_ (BUF_SIZE)]; // alignas both since sometimes accessed as array of T, and sometimes as size_t
372 };
373 DISABLE_COMPILER_MSC_WARNING_END (4324)
374
375#if qStroika_Foundation_Debug_AssertionsChecked
376 byte fGuard2_[sizeof (kGuard2_)];
377#endif
378 T* fLiveData_{};
379
380 private:
381 // generally unneeded optimization, but allows quick check of sz against just BUF_SIZE in most cases
382 constexpr bool HasEnoughCapacity_ (size_t sz) const
383 {
384 // Computing capacity - while simple and quick, is much slower than this check which
385 // is nearly always sufficient. So a slight performance tweak
386 if (sz <= BUF_SIZE) [[likely]] {
387 return true;
388 }
389 return sz <= capacity ();
390 }
391
392 private:
393 constexpr bool UsingInlinePreallocatedBuffer_ () const noexcept;
394
395 public:
396 nonvirtual void Invariant () const noexcept;
397
398 private:
399#if qStroika_Foundation_Debug_AssertionsChecked
400 nonvirtual void Invariant_ () const noexcept;
401 nonvirtual void ValidateGuards_ () const noexcept;
402#endif
403
404 private:
405 constexpr T* BufferAsT_ () noexcept;
406 constexpr const T* BufferAsT_ () const noexcept;
407
408 private:
409 static void DestroyElts_ (T* start, T* end) noexcept;
410 };
411
412}
413
414/*
415 ********************************************************************************
416 ***************************** Implementation Details ***************************
417 ********************************************************************************
418 */
419#include "InlineBuffer.inl"
420
421#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...