Flow 3.0.0
Flow project: Full implementation reference.
basic_blob.hpp
Go to the documentation of this file.
1/* Flow
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#include "flow/log/log.hpp"
23#include <boost/interprocess/smart_ptr/shared_ptr.hpp>
24#include <boost/move/make_unique.hpp>
25#include <boost/compressed_pair.hpp>
26#include <optional>
27#include <limits>
28#include <cstring>
29
30namespace flow::util
31{
32
33// Types.
34
35/**
36 * Tag type used at least in Basic_blob and Blob_with_log_context to specify that an allocated buffer be zeroed.
37 *
38 * @see util::CLEAR_ON_ALLOC, the value to pass-in to relevant APIs such as Basic_blob::resize().
39 */
41
42/**
43 * A hand-optimized and API-tweaked replacement for `vector<uint8_t>`, i.e., buffer of bytes inside an allocated area
44 * of equal or larger size; also optionally supports limited garbage-collected memory pool functionality and
45 * SHM-friendly custom-allocator support.
46 *
47 * @see Blob_with_log_context (and especially aliases #Blob and #Sharing_blob), our non-polymorphic sub-class which
48 * adds some ease of use in exchange for a small perf trade-off. (More info below under "Logging.")
49 * @see #Blob_sans_log_context + #Sharing_blob_sans_log_context, each simply an alias to
50 * `Basic_blob<std::allocator, B>` (with `B = false` or `true` respectively), in a fashion vaguely similar to
51 * what `string` is to `basic_string` (a little). This is much like #Blob/#Sharing_blob, in that it is a
52 * non-template concrete type; but does not take or store a `Logger*`.
53 *
54 * The rationale for its existence mirrors its essential differences from `vector<uint8_t>` which are as follows.
55 * To summarize, though, it exists to guarantee specific performance by reducing implementation uncertainty via
56 * lower-level operations; and force user to explicitly authorize any allocation to ensure thoughtfully performant use.
57 * Update: Plus, it adds non-prefix-sub-buffer feature, which can be useful for zero-copy deserialization.
58 * Update: Plus, it adds a simple form of garbage-collected memory pools, useful for operating multiple `Basic_blob`s
59 * that share a common over-arching memory area (buffer).
60 * Update: Plus, it adds SHM-friendly custom allocator support. (While all `vector` impls support custom allocators,
61 * only some later versions of gcc `std::vector` work with shared-memory (SHM) allocators and imperfectly at that.
62 * `boost::container::vector` a/k/a `boost::interprocess::vector` is fully SHM-friendly.)
63 *
64 * - It adds a feature over `vector<uint8_t>`: The logical contents `[begin(), end())` can optionally begin
65 * not at the start of the internally allocated buffer but somewhere past it. In other words, the logical buffer
66 * is not necessarily a prefix of the internal allocated buffer. This feature is critical when one wants
67 * to use some sub-buffer of a buffer without reallocating a smaller buffer and copying the sub-buffer into it.
68 * For example, if we read a DATA packet the majority of which is the payload, which begins a few bytes
69 * from the start -- past a short header -- it may be faster to keep passing around the whole thing with move
70 * semantics but use only the payload part, after logically
71 * deserializing it (a/k/a zero-copy deserialization semantics). Of course
72 * one can do this with `vector` as well; but one would need to always remember the prefix length even after
73 * deserializing, at which point such details would be ideally forgotten instead. So this API is significantly
74 * more pleasant in that case. Moreover it can then be used generically more easily, alongside other containers.
75 * - Its performance is guaranteed by internally executing low-level operations such as `memcpy()` directly instead of
76 * hoping that using a higher-level abstraction will ultimately do the same.
77 * - In particular, the iterator types exposed by the API *are* pointers instead of introducing any performance
78 * uncertainty by possibly using wrapper/proxy iterator class.
79 * - In particular (unless explicitly requested via optional Clear_on_alloc tag)
80 * no element or memory area is *ever* initialized to zero(es) or any other particular filler
81 * value(s). (This is surprisingly difficult to avoid with STL containers! Google it. Though, e.g.,
82 * boost.container does provide a `default_init_t` extension to various APIs like `.resize()`.) If an allocation
83 * does occur, the area is left as-is unless user specifies a source memory area from which to copy data.
84 * - However, if you *do* desire the zeroing of memory immediately upon allocation, you may request it
85 * via Clear_on_alloc tag arg to size-taking ctor, resize(), or reserve(). This is in many cases faster
86 * than an explicit `memset()` or `fill_n()` of your own; so do you use it; it is not mere syntactic sugar.
87 * - Note that I am making no assertion about `vector` being slow; the idea is to guarantee *we* aren't by removing
88 * any *question* about it; it's entirely possible a given `vector` is equally fast, but it cannot be guaranteed by
89 * standard except in terms of complexity guarantees (which is usually pretty good but not everything).
90 * - That said a quick story about `std::vector<uint8_t>` (in gcc-8.3 anyway): I (ygoldfel) once used it with
91 * a custom allocator (which worked in shared memory) and stored a megabytes-long buffer in one. Its
92 * destructor, I noticed, spent milliseconds (with 2022 hardware) -- outside the actual dealloc call.
93 * Reason: It was iterating over every (1-byte) element and invoking its (non-existent/trivial) destructor. It
94 * did not specialize to avoid this, intentionally so according to a comment, when using a custom allocator.
95 * `boost::container::vector<uint8_t>` lacked this problem; but nevertheless it shows generally written
96 * containers can have hidden such perf quirks.
97 * - To help achieve the previous bullet point, as well as to keep the code simple, the class does not parameterize
98 * on element type; it stores unsigned bytes, period (though Basic_blob::value_type is good to use if you need to
99 * refer to that type in code generically).
100 * Perhaps the same could be achieved by specialization, but we don't need the parameterization in the first place.
101 * - Unlike `vector`, it has an explicit state where there is no underlying buffer; in this case zero() is `true`.
102 * Also in that case, `capacity() == 0` and `size() == 0` (and `start() == 0`). `zero() == true` is the case on
103 * default-constructed object of this class. The reason for this is I am never sure, at least, what a
104 * default-constructed `vector` looks like internally; a null buffer always seemed like a reasonable starting point
105 * worth guaranteeing explicitly.
106 * - If `!zero()`:
107 * - make_zero() deallocates any allocated buffer and ensures zero() is `true`, as if upon default construction.
108 * - Like `vector`, it keeps an allocated memory chunk of size M, at the start of which is the
109 * logical buffer of size `N <= M`, where `N == size()`, and `M == capacity()`. However, `M >= 1` always.
110 * - There is the aforementioned added feature wherein the logical buffer begins to the right of the allocated
111 * buffer, namely at index `start()`. In this case `M >= start() + size()`, and the buffer range
112 * is in indices `[start(), start() + size())` of the allocated buffer. By default `start() == 0`, as
113 * in `vector`, but this can be changed via the 2nd, optional, argument to resize().
114 * - Like `vector`, `reserve(Mnew)`, with `Mnew <= M`, does nothing. However, unlike `vector`, the same call is
115 * *illegal* when `Mnew > M >= 1`. However, any reserve() call *is* allowed when zero() is `true`. Therefore,
116 * if the user is intentionally okay with the performance implications of a reallocation, they can call make_zero()
117 * and *then* force the reallocating reserve() call.
118 * - Like `vector`, `resize(Nnew)` merely guarantees post-condition `size() == Nnew`; which means that
119 * it is essentially equivalent to `reserve(Nnew)` followed by setting internal N member to Nnew.
120 * However, remember that resize() therefore keeps all the behaviors of reserve(), including that it cannot
121 * grow the buffer (only allocate it when zero() is `true`).
122 * - If changing `start()` from default, then: `resize(Nnew, Snew)` means `reserve(Nnew + Snew)`, plus saving
123 * internal N and S members.
124 * - The *only* way to allocate is to (directly or indirectly) call `reserve(Mnew)` when `zero() == true`.
125 * Moreover, *exactly* Mnew bytes elements are allocated and no more (unlike with `vector`, where the policy used is
126 * not known). Moreover, if `reserve(Mnew)` is called indirectly (by another method of the class), `Mnew` arg
127 * is set to no greater than size necessary to complete the operation (again, by contrast, it is unknown what `vector`
128 * does w/r/t capacity policy).
129 * - The rest of the API is common-sense but generally kept to only what has been necessary to date,
130 * in on-demand fashion.
131 *
132 * ### Optional, simple garbage-collected shared ownership functionality ###
133 * The following feature was added quite some time after `Blob` was first introduced and matured. However it seamlessly
134 * subsumes all of the above basic functionality with full backwards compatibility. It can also be disabled
135 * (and is by default) by setting #S_SHARING to `false` at compile-time. (This gains back a little bit of perf
136 * namely by turning an internal `shared_ptr` to `unique_ptr`.)
137 *
138 * The feature itself is simple: Suppose one has a blob A, constructed or otherwise `resize()`d or `reserve()`d
139 * so as to have `zero() == false`; meaning `capacity() >= 1`. Now suppose one calls the core method of this *pool*
140 * feature: share() which returns a new blob B. B will have the same exact start(), size(), capacity() -- and,
141 * in fact, the pointer `data() - start()` (i.e., the underlying buffer start pointer, buffer being capacity() long).
142 * That is, B now shares the underlying memory buffer with A. Normally, that underlying buffer would be deallocated
143 * when either `A.make_zero()` is called, or A is destructed. Now that it's shared by A and B, however,
144 * the buffer is deallocated only once make_zero() or destruction occurs for *both* A and B. That is, there is an
145 * internal (thread-safe) ref-count that must reach 0.
146 *
147 * Both A and B may now again be share()d into further sharing `Basic_blob`s. This further increments the ref-count of
148 * original buffer; all such `Basic_blob`s C, D, ... must now either make_zero() or destruct, at which point the dealloc
149 * occurs.
150 *
151 * In that way the buffer -- or *pool* -- is *garbage-collected* as a whole, with reserve() (and APIs like resize()
152 * and ctors that call it) initially allocating and setting internal ref-count to 1, share() incrementing it, and
153 * make_zero() and ~Basic_blob() decrementing it (and deallocating when ref-count=0).
154 *
155 * ### Application of shared ownership: Simple pool-of-`Basic_blob`s functionality ###
156 * The other aspect of this feature is its pool-of-`Basic_blob`s application. All of the sharing `Basic_blob`s A, B,
157 * ... retain all the aforementioned features including the ability to use resize(), start_past_prefix_inc(), etc.,
158 * to control the location of the logical sub-range [begin(), end()) within the underlying buffer (pool).
159 * E.g., suppose A was 10 bytes, with `start() = 0` and `size() = capacity() = 10`; then share() B is also that way.
160 * Now `B.start_past_prefix_inc(5); A.resize(5);` makes it so that A = the 1st 5 bytes of the pool,
161 * B the last 5 bytes (and they don't overlap -- can even be concurrently modified safely). In that way A and B
162 * are now independent `Basic_blob`s -- potentially passed, say, to independent TCP-receive calls, each of which reads
163 * up to 5 bytes -- that share an over-arching pool.
164 *
165 * The API share_after_split_left() is a convenience operation that splits a `Basic_blob`'s [begin(), end()) area into
166 * 2 areas of specified length, then returns a new Basic_blob representing the first area in the split and
167 * modifies `*this` to represent the remainder (the 2nd area). This simply performs the op described in the preceding
168 * paragraph. share_after_split_right() is similar but acts symmetrically from the right. Lastly
169 * `share_after_split_equally*()` splits a Basic_blob into several equally-sized (except the last one potentially)
170 * sub-`Basic_blob`s of size N, where N is an arg. (It can be thought of as just calling `share_after_split_left(N)`
171 * repeatedly, then returning a sequence of the resulting post-split `Basic_blob`s.)
172 *
173 * To summarize: The `share_after_split*()` APIs are useful to divide (potentially progressively) a pool into
174 * non-overlapping `Basic_blob`s within a pool while ensuring the pool continues to exist while `Basic_blob`s refer
175 * to any part of it (but no later). Meanwhile direct use of share() with resize() and `start_past_prefix*()` allows
176 * for overlapping such sharing `Basic_blob`s.
177 *
178 * Note that deallocation occurs regardless of which areas of that pool the relevant `Basic_blob`s represent,
179 * and whether they overlap or not (and, for that matter, whether they even together make up the entire pool or
180 * leave "gaps" in-between). The whole pool is deallocated the moment the last of the co-owning `Basic_blob`s
181 * performs either make_zero() (or an equivalent like `assign({})`) or ~Basic_blob() -- the values of start() and
182 * size() at the time are not relevant.
183 *
184 * ### Custom allocator (and SHared Memory) support ###
185 * Like STL containers this one optionally takes a custom allocator type (#Allocator) as a compile-time parameter
186 * instead of using the regular heap (`std::allocator`). Unlike many STL container implementations, including
187 * at least older `std::vector`, it supports SHM-storing allocators without a constant cross-process vaddr scheme.
188 * (Some do support this but with surprising perf flaws when storing raw integers/bytes. boost.container `vector`
189 * has solid support but lacks various other properties of Basic_blob.) While a detailed discussion is outside
190 * our scope here, the main point is internally `*this` stores no raw `value_type*` but rather
191 * `Allocator::pointer` -- which in many cases *is* `value_type*`; but for advanced applications like SHM
192 * it might be a fancy-pointer like `boost::interprocess::offset_ptr<value_type>`. For general education
193 * check out boost.interprocess docs covering storage of STL containers in SHM. (However note that the
194 * allocators provided by that library are only one option even for SHM storage alone; e.g., they are stateful,
195 * and often one would like a stateless -- zero-size -- allocator. Plus there are other limitations to
196 * boost.interprocess SHM support, robust though it is.)
197 *
198 * @note In the somewhat-exotic case wherein #Allocator is stateful (therefore not `std::allocator` default),
199 * such that it is possible for two objects of that type to value-compare as not-equal, the following rules
200 * apply. Propagation of allocators via move-ct, copy-ct, move-assign, copy-assign, or swap follows standard
201 * rules (see cppreference.com or the like for those). This is normal. However the following is different
202 * from at least some standard containers and derivatives (e.g., boost.container ones), at least potentially:
203 * Even if an aforementioned op *did* propagate the allocator object from the source `Basic_blob`,
204 * any existing buffer (meaning `!zero()`) shall be deallocated using the same allocator object that allocated
205 * it originally (hence in this scenario there are now 2 allocator objects stored in `*this`). A reallocation
206 * with the new allocator object will *not* be forced. (Among other considerations this means that original
207 * allocator's resources -- the source pool or whatever it is -- must stay alive until the deallocation does
208 * occur according to the simple above-documented rules of when that must happen.) The rationale is that
209 * Basic_blob is biased toward simple, predictable behavior w/r/t deallocs and allocs occurring, even in the
210 * fact of exotic get_allocator() changes.
211 *
212 * ### Logging ###
213 * When and if `*this` logs, it is with log::Sev::S_TRACE severity or more verbose.
214 *
215 * Unlike many other Flow API classes this one does not derive from log::Log_context nor take a `Logger*` in
216 * ctor (and store it). Instead each API method/ctor/function capable of logging takes an optional
217 * (possibly null) log::Logger pointer. If supplied it's used by that API alone (with some minor async exceptions).
218 * If you would like more typical Flow-style logging API then use our non-polymorphic sub-class Blob_with_log_context
219 * (more likely aliases #Blob, #Sharing_blob). However consider the following first.
220 *
221 * Why this design? Answer:
222 * - Basic_blob is meant to be lean, both in terms of RAM used and processor cycles spent. Storing a `Logger*`
223 * takes some space; and storing it, copying/moving it, etc., takes a little compute. In a low-level
224 * API like Basic_blob this is potentially nice to avoid when not actively needed. (That said the logging
225 * can be extremely useful when debugging and/or profiling RAM use + allocations.)
226 * - This isn't a killer. The original `Blob` (before Basic_blob existed) stored a `Logger*`, and it was fine.
227 * However:
228 * - Storing a `Logger*` is always okay when `*this` itself is stored in regular heap or on the stack.
229 * However, `*this` itself may be stored in SHM; #Allocator parameterization (see above regarding
230 * "Custom allocator") suggests as much (i.e., if the buffer is stored in SHM, we might be too).
231 * In that case `Logger*` does not, usually, make sense. As of this writing `Logger` in process 1
232 * has no relationship with any `Logger` in process 2; and even if the `Logger` were stored in SHM itself,
233 * `Logger` would need to be supplied via an in-SHM fancy-pointer, not `Logger*`, typically. The latter is
234 * a major can of worms and not supported by flow::log in any case as of this writing.
235 * - Therefore, even if we don't care about RAM/perf implications of storing `Logger*` with the blob, at least
236 * in some real applications it makes no sense.
237 *
238 * #Blob/#Sharing_blob provides this support while ensuring #Allocator (no longer a template parameter in its case)
239 * is the vanilla `std::allocator`. The trade-off is as noted just above.
240 *
241 * ### Thread safety ###
242 * Before share() (or `share_*()`) is called: Essentially: Thread safety is the same as for `vector<uint8_t>`.
243 *
244 * Without `share*()` any two Basic_blob objects refer to separate areas in memory; hence it is safe to access
245 * Basic_blob A concurrently with accessing Basic_blob B in any fashion (read, write).
246 *
247 * However: If 2 `Basic_blob`s A and B co-own a pool, via a `share*()` chain, then concurrent write and read/write
248 * to A and B respectively are thread-safe if and only if their [begin(), end()) ranges don't overlap. Otherwise,
249 * naturally, one would be writing to an area while it is being read simultaneously -- not safe.
250 *
251 * Tip: When working in `share*()` mode, exclusive use of `share_after_split*()` is a great way to guarantee no 2
252 * `Basic_blob`s ever overlap. Meanwhile one must be careful when using share() directly and/or subsequently sliding
253 * the range around via resize(), `start_past_prefix*()`: `A.share()` and A not only (originally) overlap but
254 * simply represent the same area of memory; and resize() and co. can turn a non-overlapping range into an overlapping
255 * one (encroaching on someone else's "territory" within the pool).
256 *
257 * @todo Write a class template, perhaps `Tight_blob<Allocator, bool>`, which would be identical to Basic_blob
258 * but forego the framing features, namely size() and start(), thus storing only the RAII array pointer data()
259 * and capacity(); rewrite Basic_blob in terms of this `Tight_blob`. This simple container type has had some demand
260 * in practice, and Basic_blob can and should be cleanly built on top of it (perhaps even as an IS-A subclass).
261 *
262 * @tparam Allocator_t
263 * An allocator, with `value_type` equal to our #value_type, per the standard C++1x `Allocator` concept.
264 * In most uses this shall be left at the default `std::allocator<value_type>` which allocates in
265 * standard heap (`new[]`, `delete[]`). A custom allocator may be used instead. SHM-storing allocators,
266 * and generally allocators for which `pointer` is not simply `value_type*` but rather a fancy-pointer
267 * (see cppreference.com) are correctly supported. (Note this may not be the case for your compiler's
268 * `std::vector`.)
269 * @tparam SHARING
270 * If `true`, share() and all derived methods, plus blobs_sharing(), can be instantiated (invoked in compiled
271 * code). If `false` they cannot (`static_assert()` will trip), but the resulting Basic_blob concrete
272 * class will be slightly more performant (internally, a `shared_ptr` becomes instead a `unique_ptr` which
273 * means smaller allocations and no ref-count logic invoked).
274 */
275template<typename Allocator_t, bool SHARING>
277{
278public:
279 // Types.
280
281 /// Short-hand for values, which in this case are unsigned bytes.
283
284 /// Type for index into blob or length of blob or sub-blob.
285 using size_type = std::size_t;
286
287 /// Type for difference of `size_type`s.
288 using difference_type = std::ptrdiff_t;
289
290 /// Type for iterator pointing into a mutable structure of this type.
292
293 /// Type for iterator pointing into an immutable structure of this type.
294 using Const_iterator = value_type const *;
295
296 /// Short-hand for the allocator type specified at compile-time. Its element type is our #value_type.
297 using Allocator = Allocator_t;
298 static_assert(std::is_same_v<typename Allocator::value_type, value_type>,
299 "Allocator template param must be of form A<V> where V is our value_type.");
300
301 /// For container compliance (hence the irregular capitalization): pointer to element.
303 /// For container compliance (hence the irregular capitalization): pointer to `const` element.
305 /// For container compliance (hence the irregular capitalization): reference to element.
307 /// For container compliance (hence the irregular capitalization): reference to `const` element.
309 /// For container compliance (hence the irregular capitalization): #Iterator type.
311 /// For container compliance (hence the irregular capitalization): #Const_iterator type.
313
314 // Constants.
315
316 /// Value of template parameter `SHARING` (for generic programming).
317 static constexpr bool S_SHARING = SHARING;
318
319 /// Special value indicating an unchanged `size_type` value; such as in resize().
320 static constexpr size_type S_UNCHANGED = size_type(-1); // Same trick as std::string::npos.
321
322 /**
323 * `true` if #Allocator underlying allocator template is simply `std::allocator`; `false`
324 * otherwise.
325 *
326 * Note that if this is `true`, it may be worth using #Blob/#Sharing_blob, instead of its `Basic_blob<std::allocator>`
327 * super-class; at the cost of a marginally larger RAM footprint (an added `Logger*`) you'll get a more convenient
328 * set of logging API knobs (namely `Logger*` stored permanently from construction; and there will be no need to
329 * supply it as arg to subsequent APIs when logging is desired).
330 *
331 * ### Implications of #S_IS_VANILLA_ALLOC being `false` ###
332 * This is introduced in our class doc header. Briefly however:
333 * - The underlying buffer, if any, and possibly some small aux data shall be allocated
334 * via #Allocator, not simply the regular heap's `new[]` and/or `new`.
335 * - They shall be deallocated, if needed, via #Allocator, not simply the regular heap's
336 * `delete[]` and/or `delete`.
337 * - Because storing a pointer to log::Logger may be meaningless when storing in an area allocated
338 * by some custom allocators (particularly SHM-heap ones), we shall not auto-TRACE-log on dealloc.
339 * - This caveat applies only if #S_SHARING is `true`.
340 *
341 * @internal
342 * - (If #S_SHARING)
343 * Accordingly the ref-counted buffer pointer buf_ptr() shall be a `boost::interprocess::shared_ptr`
344 * instead of a vanilla `shared_ptr`; the latter may be faster and more full-featured, but it is likely
345 * to internally store a raw `T*`; we need one that stores an `Allocator::pointer` instead;
346 * e.g., a fancy-pointer type (like `boost::interprocess::offset_ptr`) when dealing with
347 * SHM-heaps (typically).
348 * - If #S_IS_VANILLA_ALLOC is `true`, then we revert to the faster/more-mature/full-featured
349 * `shared_ptr`. In particular it is faster (if used with `make_shared()` and similar) by storing
350 * the user buffer and aux data/ref-count in one contiguously-allocated buffer.
351 * - (If #S_SHARING is `false`)
352 * It's a typical `unique_ptr` template either way (because it supports non-raw-pointer storage out of the
353 * box) but:
354 * - A custom deleter is necessary similarly to the above.
355 * - Its `pointer` member alias crucially causes the `unique_ptr` to store
356 * an `Allocator::pointer` instead of a `value_type*`.
357 *
358 * See #Buf_ptr doc header regarding the latter two bullet points.
359 */
360 static constexpr bool S_IS_VANILLA_ALLOC = std::is_same_v<Allocator, std::allocator<value_type>>;
361
362 // Constructors/destructor.
363
364 /**
365 * Constructs blob with `zero() == true`. Note this means no buffer is allocated.
366 *
367 * @param alloc_raw_src
368 * Allocator to copy and store in `*this` for all buffer allocations/deallocations.
369 * If #Allocator is stateless, then this has size zero, so nothing is copied at runtime,
370 * and by definition it is to equal `Allocator{}`.
371 */
372 Basic_blob(const Allocator& alloc_raw_src = {});
373
374 /**
375 * Constructs blob with size() and capacity() equal to the given `size`, and `start() == 0`. Performance note:
376 * elements are not initialized to zero or any other value. A new over-arching buffer (pool) is therefore allocated.
377 *
378 * @see a similar ctor that takes Clear_on_alloc tag arg, if you *do* want the elements to be zero-initialized.
379 * Doing so is often faster than your own explicit `memset(X.data(), 0, X.size())` (or similar).
380 *
381 * Corner case note: a post-condition is `zero() == (size() == 0)`. Note, also, that the latter is *not* a universal
382 * invariant (see zero() doc header).
383 *
384 * Formally: If `size >= 1`, then a buffer is allocated; and the internal ownership ref-count is set to 1.
385 *
386 * @param size
387 * A non-negative desired size.
388 * @param logger_ptr
389 * The Logger implementation to use in *this* routine (synchronously) or asynchronously when TRACE-logging
390 * in the event of buffer dealloc. Null allowed.
391 * @param alloc_raw_src
392 * Allocator to copy and store in `*this` for all buffer allocations/deallocations.
393 * If #Allocator is stateless, then this has size zero, so nothing is copied at runtime,
394 * and by definition it is to equal `Allocator{}`.
395 */
396 explicit Basic_blob(size_type size, log::Logger* logger_ptr = nullptr,
397 const Allocator& alloc_raw_src = {});
398
399 /**
400 * Identical to similar-sig ctor except, if `size > 0`, all `size` elements are performantly initialized to zero.
401 *
402 * Using this ctor form, instead of using the non-init one followed by your own explicit
403 * `memset(X.data(), 0, X.size())` (or similar), is likely to be significantly faster in at least some cases.
404 * It is *not* mere syntactic sugar.
405 *
406 * @see resize() and reserve() also have Clear_on_alloc forms.
407 *
408 * @param coa_tag
409 * API-choosing tag util::CLEAR_ON_ALLOC.
410 * @param size
411 * See similar ctor.
412 * @param logger_ptr
413 * See similar ctor.
414 * @param alloc_raw_src
415 * See similar ctor.
416 */
417 explicit Basic_blob(size_type size, Clear_on_alloc coa_tag, log::Logger* logger_ptr = nullptr,
418 const Allocator& alloc_raw_src = {});
419
420 /**
421 * Move constructor, constructing a blob exactly internally equal to pre-call `moved_src`, while the latter is
422 * made to be exactly as if it were just constructed as `Basic_blob{nullptr}` (allocator subtleties aside).
423 * Performance: constant-time, at most copying a few scalars.
424 *
425 * @note It is important this be `noexcept`, if a copying counterpart to us exists in this class; otherwise
426 * (e.g.) `vector<Basic_blob>` will, on realloc, default to copying `*this`es around instead of moving:
427 * a terrible (in its stealthiness) perf loss.
428 *
429 * @param moved_src
430 * The object whose internals to move to `*this` and replace with a blank-constructed object's internals.
431 * @param logger_ptr
432 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
433 */
434 Basic_blob(Basic_blob&& moved_src, log::Logger* logger_ptr = nullptr) noexcept;
435
436 /**
437 * Copy constructor, constructing a blob logically equal to `src`. More formally, guarantees post-condition wherein
438 * `[this->begin(), this->end())` range is equal by value (including length) to `src` equivalent range but no memory
439 * overlap. A post-condition is `capacity() == size()`, and `start() == 0`. Performance: see copying assignment
440 * operator.
441 *
442 * Corner case note: the range equality guarantee includes the degenerate case where that range is empty, meaning we
443 * simply guarantee post-condition `src.empty() == this->empty()`.
444 *
445 * Corner case note 2: post-condition: `this->zero() == this->empty()`
446 * (note `src.zero()` state is not necessarily preserved in `*this`).
447 *
448 * Note: This is `explicit`, which is atypical for a copy constructor, to generate compile errors in hard-to-see
449 * (and often unintentional) instances of copying. Copies of Basic_blob should be quite intentional and explicit.
450 * (One example where one might forget about a copy would be when using a Basic_blob argument without `cref` or
451 * `ref` in a `bind()`; or when capturing by value, not by ref, in a lambda.)
452 *
453 * Formally: If `src.size() >= 1`, then a buffer is allocated; and the internal ownership ref-count is set to 1.
454 *
455 * @param src
456 * Object whose range of bytes of length `src.size()` starting at `src.begin()` is copied into `*this`.
457 * @param logger_ptr
458 * The Logger implementation to use in *this* routine (synchronously) or asynchronously when TRACE-logging
459 * in the event of buffer dealloc. Null allowed.
460 */
461 explicit Basic_blob(const Basic_blob& src, log::Logger* logger_ptr = nullptr);
462
463 /**
464 * Destructor that drops `*this` ownership of the allocated internal buffer if any, as by make_zero();
465 * if no other Basic_blob holds ownership of that buffer, then that buffer is deallocated also.
466 * Recall that other `Basic_blob`s can only gain co-ownership via `share*()`; hence if one does not use that
467 * feature, the destructor will in fact deallocate the buffer (if any).
468 *
469 * Formally: If `!zero()`, then the internal ownership ref-count is decremented by 1, and if it reaches
470 * 0, then a buffer is deallocated.
471 *
472 * ### Logging ###
473 * This will not log, as it is not possible to pass a `Logger*` to a dtor without storing it (which we avoid
474 * for reasons outlined in class doc header). Use #Blob/#Sharing_blob if it is important to log in this situation
475 * (although there are some minor trade-offs).
476 */
478
479 // Methods.
480
481 /**
482 * Move assignment. Allocator subtleties aside and assuming `this != &moved_src` it is equivalent to:
483 * `make_zero(); this->swap(moved_src, logger_ptr)`. (If `this == &moved_src`, this is a no-op.)
484 *
485 * @param moved_src
486 * See swap().
487 * @param logger_ptr
488 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
489 * @return `*this`.
490 */
491 Basic_blob& assign(Basic_blob&& moved_src, log::Logger* logger_ptr = nullptr) noexcept;
492
493 /**
494 * Move assignment operator (no logging): equivalent to `assign(std::move(moved_src))`.
495 *
496 * @note It is important this be `noexcept`, if a copying counterpart to us exists in this class; otherwise
497 * (e.g.) `vector<Basic_blob>` will, on realloc, default to copying `*this`es around instead of moving:
498 * a terrible (in its stealthiness) perf loss.
499 *
500 * @param moved_src
501 * See assign() (move overload).
502 * @return `*this`.
503 */
504 Basic_blob& operator=(Basic_blob&& moved_src) noexcept;
505
506 /**
507 * Copy assignment: assuming `(this != &src) && (!blobs_sharing(*this, src))`,
508 * makes `*this` logically equal to `src`; but behavior undefined if a reallocation would be necessary to do this.
509 * (If `this == &src`, this is a no-op. If not but `blobs_sharing(*this, src) == true`, see "Sharing blobs" below.
510 * This is assumed to not be the case in further discussion.)
511 *
512 * More formally:
513 * no-op if `this == &src`; "Sharing blobs" behavior if not so, but `src` shares buffer with `*this`; otherwise:
514 * Guarantees post-condition wherein `[this->begin(), this->end())` range is equal
515 * by value (including length) to `src` equivalent range but no memory overlap. Post-condition: `start() == 0`;
516 * capacity() either does not change or equals size(). capacity() growth is not allowed: behavior is undefined if
517 * `src.size()` exceeds pre-call `this->capacity()`, unless `this->zero() == true` pre-call. Performance: at most a
518 * memory area of size `src.size()` is copied and some scalars updated; a memory area of that size is allocated only
519 * if required; no ownership drop or deallocation occurs.
520 *
521 * Corner case note: the range equality guarantee includes the degenerate case where that range is empty, meaning we
522 * simply guarantee post-condition `src.empty() == this->empty()`.
523 *
524 * Corner case note 2: post-condition: if `this->empty() == true` then `this.zero()` has the same value as at entry
525 * to this call. In other words, no deallocation occurs, even if
526 * `this->empty() == true` post-condition holds; at most internally a scalar storing size is assigned 0.
527 * (You may force deallocation in that case via make_zero() post-call, but this means you'll have to intentionally
528 * perform that relatively slow op.)
529 *
530 * As with reserve(), IF pre-condition `zero() == false`, THEN pre-condition `src.size() <= this->capacity()`
531 * must hold, or behavior is undefined (i.e., as noted above, capacity() growth is not allowed except from 0).
532 * Therefore, NO REallocation occurs! However, also as with reserve(), if you want to intentionally allow such a
533 * REallocation, then simply first call make_zero(); then execute the
534 * `assign()` copy as planned. This is an intentional restriction forcing caller to explicitly allow a relatively
535 * slow reallocation op.
536 *
537 * Formally: If `src.size() >= 1`, and `zero() == true`, then a buffer is allocated; and the internal ownership
538 * ref-count is set to 1.
539 *
540 * ### Sharing blobs ###
541 * If `blobs_sharing(*this, src) == true`, meaning the target and source are operating on the same buffer, then
542 * behavior is undefined (assertion may trip). Rationale for this design is as follows. The possibilities were:
543 * -# Undefined behavior/assertion.
544 * -# Just adjust `this->start()` and `this->size()` to match `src`; continue co-owning the underlying buffer;
545 * copy no data.
546 * -# `this->make_zero()` -- losing `*this` ownership, while `src` keeps it -- and then allocate a new buffer
547 * and copy `src` data into it.
548 *
549 * Choosing between these is tough, as this is an odd corner case. 3 is not criminal, but generally no method
550 * ever forces make_zero() behavior, always leaving it to the user to consciously do, so it seems prudent to keep
551 * to that practice (even though this case is a bit different from, say, resize() -- since make_zero() here has
552 * no chance to deallocate anything, only decrement ref-count). 2 is performant and slick but suggests a special
553 * behavior in a corner case; this *feels* slightly ill-advised in a standard copy assignment operator. Therefore
554 * it seems better to crash-and-burn (choice 1), in the same way an attempt to resize()-higher a non-`zero()` blob
555 * would crash and burn, forcing the user to explicitly execute what they want. After all, 3 is done by simply
556 * calling make_zero() first; and 2 is possible with a simple resize() call; and the blobs_sharing() check is both
557 * easy and performant.
558 *
559 * @warning A post-condition is `start() == 0`; meaning `start()` at entry is ignored and reset to 0; the entire
560 * (co-)owned buffer -- if any -- is potentially used to store the copied values. In particular, if one
561 * plans to work on a sub-blob of a shared pool (see class doc header), then using this assignment op is
562 * not advised. Use emplace_copy() instead; or perform your own copy onto
563 * mutable_buffer().
564 *
565 * @param src
566 * Object whose range of bytes of length `src.size()` starting at `src.begin()` is copied into `*this`.
567 * Behavior is undefined if pre-condition is `!zero()`, and this memory area overlaps at any point with the
568 * memory area of same size in `*this` (unless that size is zero -- a degenerate case).
569 * (This can occur only via the use of `share*()` -- otherwise `Basic_blob`s always refer to separate areas.)
570 * Also behavior undefined if pre-condition is `!zero()`, and `*this` (co-)owned buffer is too short to
571 * accomodate all `src.size()` bytes (assertion may trip).
572 * @param logger_ptr
573 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
574 * @return `*this`.
575 */
576 Basic_blob& assign(const Basic_blob& src, log::Logger* logger_ptr = nullptr);
577
578 /**
579 * Copy assignment operator (no logging): equivalent to `assign(src)`.
580 *
581 * @param src
582 * See assign() (copy overload).
583 * @return `*this`.
584 */
585 Basic_blob& operator=(const Basic_blob& src);
586
587 /**
588 * Swaps the contents of this structure and `other`, or no-op if `this == &other`. Performance: at most this
589 * involves swapping a few scalars which is constant-time.
590 *
591 * @param other
592 * The other structure.
593 * @param logger_ptr
594 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
595 */
596 void swap(Basic_blob& other, log::Logger* logger_ptr = nullptr) noexcept;
597
598 /**
599 * Applicable to `!zero()` blobs, this returns an identical Basic_blob that shares (co-owns) `*this` allocated buffer
600 * along with `*this` and any other `Basic_blob`s also sharing it. Behavior is undefined (assertion may trip) if
601 * `zero() == true`: it is nonsensical to co-own nothing; just use the default ctor then.
602 *
603 * The returned Basic_blob is identical in that not only does it share the same memory area (hence same capacity())
604 * but has identical start(), size() (and hence begin() and end()). If you'd like to work on a different
605 * part of the allocated buffer, please consider `share_after_split*()` instead; the pool-of-sub-`Basic_blob`s
606 * paradigm suggested in the class doc header is probably best accomplished using those methods and not share().
607 *
608 * You can also adjust various sharing `Basic_blob`s via resize(), start_past_prefix_inc(), etc., directly -- after
609 * share() returns.
610 *
611 * Formally: Before this returns, the internal ownership ref-count (shared among `*this` and the returned
612 * Basic_blob) is incremented.
613 *
614 * @param logger_ptr
615 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
616 * @return An identical Basic_blob to `*this` that shares the underlying allocated buffer. See above.
617 */
618 Basic_blob share(log::Logger* logger_ptr = nullptr) const;
619
620 /**
621 * Applicable to `!zero()` blobs, this shifts `this->begin()` by `size` to the right without changing
622 * end(); and returns a Basic_blob containing the shifted-past values that shares (co-owns) `*this` allocated buffer
623 * along with `*this` and any other `Basic_blob`s also sharing it.
624 *
625 * More formally, this is identical to simply `auto b = share(); b.resize(size); start_past_prefix_inc(size);`.
626 *
627 * This is useful when working in the pool-of-sub-`Basic_blob`s paradigm. This and other `share_after_split*()`
628 * methods are usually better to use rather than share() directly (for that paradigm).
629 *
630 * Behavior is undefined (assertion may trip) if `zero() == true`.
631 *
632 * Corner case: If `size > size()`, then it is taken to equal size().
633 *
634 * Degenerate case: If `size` (or size(), whichever is smaller) is 0, then this method is identical to
635 * share(). Probably you don't mean to call share_after_split_left() in that case, but it's your decision.
636 *
637 * Degenerate case: If `size == size()` (and not 0), then `this->empty()` becomes `true` -- though
638 * `*this` continues to share the underlying buffer despite [begin(), end()) becoming empty. Typically this would
639 * only be done as, perhaps, the last iteration of some progressively-splitting loop; but it's your decision.
640 *
641 * Formally: Before this returns, the internal ownership ref-count (shared among `*this` and the returned
642 * Basic_blob) is incremented.
643 *
644 * @param size
645 * Desired size() of the returned Basic_blob; and the number of elements by which `this->begin()` is
646 * shifted right (hence start() is incremented). Any value exceeding size() is taken to equal it.
647 * @param logger_ptr
648 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
649 * @return The split-off-on-the-left Basic_blob that shares the underlying allocated buffer with `*this`. See above.
650 */
651 Basic_blob share_after_split_left(size_type size, log::Logger* logger_ptr = nullptr);
652
653 /**
654 * Identical to share_after_split_left(), except `this->end()` shifts by `size` to the left (instead of
655 * `this->begin()` to the right), and the split-off Basic_blob contains the *right-most* `size` elements
656 * (instead of the left-most).
657 *
658 * More formally, this is identical to simply
659 * `auto lt_size = size() - size; auto b = share(); resize(lt_size); b.start_past_prefix_inc(lt_size);`.
660 * Cf. share_after_split_left() formal definition and note the symmetry.
661 *
662 * All other characteristics are as written for share_after_split_left().
663 *
664 * @param size
665 * Desired size() of the returned Basic_blob; and the number of elements by which `this->end()` is
666 * shifted left (hence size() is decremented). Any value exceeding size() is taken to equal it.
667 * @param logger_ptr
668 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
669 * @return The split-off-on-the-right Basic_blob that shares the underlying allocated buffer with `*this`. See above.
670 */
671 Basic_blob share_after_split_right(size_type size, log::Logger* logger_ptr = nullptr);
672
673 /**
674 * Identical to successively performing `share_after_split_left(size)` until `this->empty() == true`;
675 * the resultings `Basic_blob`s are emitted via `emit_blob_func()` callback in the order they're split off from
676 * the left. In other words this partitions a non-zero() `Basic_blob` -- perhaps typically used as a pool --
677 * into equally-sized (except possibly the last one) adjacent sub-`Basic_blob`s.
678 *
679 * A post-condition is that `empty() == true` (`size() == 0`). In addition, if `headless_pool == true`,
680 * then `zero() == true` is also a post-condition; i.e., the pool is "headless": it disappears once all the
681 * resulting sub-`Basic_blob`s drop their ownership (as well as any other co-owning `Basic_blob`s).
682 * Otherwise, `*this` will continue to share the pool despite size() becoming 0. (Of course, even then, one is
683 * free to make_zero() or destroy `*this` -- the former, before returning, is all that `headless_pool == true`
684 * really adds.)
685 *
686 * Behavior is undefined (assertion may trip) if `empty() == true` (including if `zero() == true`, but even if not)
687 * or if `size == 0`.
688 *
689 * @see share_after_split_equally_emit_seq() for a convenience wrapper to emit to, say, `vector<Basic_blob>`.
690 * @see share_after_split_equally_emit_ptr_seq() for a convenience wrapper to emit to, say,
691 * `vector<unique_ptr<Basic_blob>>`.
692 *
693 * @tparam Emit_blob_func
694 * A callback compatible with signature `void F(Basic_blob&& blob_moved)`.
695 * @param size
696 * Desired size() of each successive out-Basic_blob, except the last one. Behavior undefined (assertion may
697 * trip) if not positive.
698 * @param headless_pool
699 * Whether to perform `this->make_zero()` just before returning. See above.
700 * @param emit_blob_func
701 * `F` such that `F(std::move(blob))` shall be called with each successive sub-Basic_blob.
702 * @param logger_ptr
703 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
704 */
705 template<typename Emit_blob_func>
706 void share_after_split_equally(size_type size, bool headless_pool, Emit_blob_func&& emit_blob_func,
707 log::Logger* logger_ptr = nullptr);
708
709 /**
710 * share_after_split_equally() wrapper that places `Basic_blob`s into the given container via
711 * `push_back()`.
712 *
713 * @tparam Blob_container
714 * Something with method compatible with `push_back(Basic_blob&& blob_moved)`.
715 * @param size
716 * See share_after_split_equally().
717 * @param headless_pool
718 * See share_after_split_equally().
719 * @param out_blobs
720 * `out_blobs->push_back()` shall be executed 1+ times.
721 * @param logger_ptr
722 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
723 */
724 template<typename Blob_container>
725 void share_after_split_equally_emit_seq(size_type size, bool headless_pool, Blob_container* out_blobs,
726 log::Logger* logger_ptr = nullptr);
727
728 /**
729 * share_after_split_equally() wrapper that places `Ptr<Basic_blob>`s into the given container via
730 * `push_back()`, where the type `Ptr<>` is determined via `Blob_ptr_container::value_type`.
731 *
732 * @tparam Blob_ptr_container
733 * Something with method compatible with `push_back(Ptr&& blob_ptr_moved)`,
734 * where `Ptr` is `Blob_ptr_container::value_type`, and `Ptr{new Basic_blob}` can be created.
735 * `Ptr` is to be a smart pointer type such as `unique_ptr<Basic_blob>` or `shared_ptr<Basic_blob>`.
736 * @param size
737 * See share_after_split_equally().
738 * @param headless_pool
739 * See share_after_split_equally().
740 * @param out_blobs
741 * `out_blobs->push_back()` shall be executed 1+ times.
742 * @param logger_ptr
743 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
744 */
745 template<typename Blob_ptr_container>
746 void share_after_split_equally_emit_ptr_seq(size_type size, bool headless_pool, Blob_ptr_container* out_blobs,
747 log::Logger* logger_ptr = nullptr);
748
749 /**
750 * Replaces logical contents with a copy of the given non-overlapping area anywhere in memory. More formally:
751 * This is exactly equivalent to copy-assignment (`*this = b`), where `const Basic_blob b` owns exactly
752 * the memory area given by `src`. However, note the newly relevant restriction documented for `src` parameter below
753 * (no overlap allowed).
754 *
755 * All characteristics are as written for the copy assignment operator, including "Formally" and the warning.
756 *
757 * @param src
758 * Source memory area. Behavior is undefined if pre-condition is `!zero()`, and this memory area overlaps
759 * at any point with the memory area of same size at `begin()`. Otherwise it can be anywhere at all.
760 * Also behavior undefined if pre-condition is `!zero()`, and `*this` (co-)owned buffer is too short to
761 * accomodate all `src.size()` bytes (assertion may trip).
762 * @param logger_ptr
763 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
764 * @return Number of elements copied, namely `src.size()`, or simply size().
765 */
766 size_type assign_copy(const boost::asio::const_buffer& src, log::Logger* logger_ptr = nullptr);
767
768 /**
769 * Copies `src` buffer directly onto equally sized area within `*this` at location `dest`; `*this` must have
770 * sufficient size() to accomodate all of the data copied. Performance: The only operation performed is a copy from
771 * `src` to `dest` using the fastest reasonably available technique.
772 *
773 * None of the following changes: zero(), empty(), size(), capacity(), begin(), end(); nor the location (or size) of
774 * internally stored buffer.
775 *
776 * @param dest
777 * Destination location within this blob. This must be in `[begin(), end()]`; and,
778 * unless `src.size() == 0`, must not equal end() either.
779 * @param src
780 * Source memory area. Behavior is undefined if this memory area overlaps
781 * at any point with the memory area of same size at `dest` (unless that size is zero -- a degenerate
782 * case). Otherwise it can be anywhere at all, even partially or fully within `*this`.
783 * Also behavior undefined if `*this` blob is too short to accomodate all `src.size()` bytes
784 * (assertion may trip).
785 * @param logger_ptr
786 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
787 * @return Location in this blob just past the last element copied; `dest` if none copied; in particular end() is a
788 * possible value.
789 */
790 Iterator emplace_copy(Const_iterator dest, const boost::asio::const_buffer& src, log::Logger* logger_ptr = nullptr);
791
792 /**
793 * The opposite of emplace_copy() in every way, copying a sub-blob onto a target memory area. Note that the size
794 * of that target buffer (`dest.size()`) determines how much of `*this` is copied.
795 *
796 * @param src
797 * Source location within this blob. This must be in `[begin(), end()]`; and,
798 * unless `dest.size() == 0`, must not equal end() either.
799 * @param dest
800 * Destination memory area. Behavior is undefined if this memory area overlaps
801 * at any point with the memory area of same size at `src` (unless that size is zero -- a degenerate
802 * case). Otherwise it can be anywhere at all, even partially or fully within `*this`.
803 * Also behavior undefined if `src + dest.size()` is past end of `*this` blob (assertion may trip).
804 * @param logger_ptr
805 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
806 * @return Location in this blob just past the last element copied; `src` if none copied; end() is a possible value.
807 */
809 log::Logger* logger_ptr = nullptr) const;
810
811 /**
812 * Returns number of elements stored, namely `end() - begin()`. If zero(), this is 0; but if this is 0, then
813 * zero() may or may not be `true`.
814 *
815 * @return See above.
816 */
818
819 /**
820 * Returns the offset between `begin()` and the start of the internally allocated buffer. If zero(), this is 0; but
821 * if this is 0, then zero() may or may not be `true`.
822 *
823 * @return See above.
824 */
826
827 /**
828 * Returns `size() == 0`. If zero(), this is `true`; but if this is `true`, then
829 * zero() may or may not be `true`.
830 *
831 * @return See above.
832 */
833 bool empty() const;
834
835 /**
836 * Returns the number of elements in the internally allocated buffer, which is 1 or more; or 0 if no buffer
837 * is internally allocated. Some formal invariants: `(capacity() == 0) == zero()`; `start() + size() <= capacity()`.
838 *
839 * See important notes on capacity() policy in the class doc header.
840 *
841 * @return See above.
842 */
844
845 /**
846 * Returns `false` if a buffer is allocated and owned; `true` otherwise. See important notes on how this relates
847 * to empty() and capacity() in those methods' doc headers. See also other important notes in class doc header.
848 *
849 * Note that zero() is `true` for any default-constructed Basic_blob.
850 *
851 * @return See above.
852 */
853 bool zero() const;
854
855 /**
856 * Ensures that an internal buffer of at least `capacity` elements is allocated and owned; disallows growing an
857 * existing buffer; never shrinks an existing buffer; if a buffer is allocated, it is no larger than `capacity`.
858 *
859 * @see a similar overload that takes Clear_on_alloc tag arg, if you *do* want the elements to be zero-initialized.
860 * Doing so is often faster than your own explicit `memset(X.data(), 0, X.size())` (or similar).
861 *
862 * reserve() may be called directly but should be formally understood to be called by resize(), assign_copy(),
863 * copy assignment operator, copy constructor. In all cases, the value passed to reserve() is exactly the size
864 * needed to perform the particular task -- no more (and no less). As such, reserve() policy is key to knowing
865 * how the class behaves elsewhere. See class doc header for discussion in larger context.
866 *
867 * Performance/behavior: If zero() is true pre-call, `capacity`-sized buffer is allocated. Otherwise,
868 * no-op if `capacity <= capacity()` pre-call. Behavior is undefined if `capacity > capacity()` pre-call
869 * (again, unless zero(), meaning `capacity() == 0`). In other words, no deallocation occurs, and an allocation
870 * occurs only if necessary. Growing an existing buffer is disallowed. However, if you want to intentionally
871 * REallocate, then simply first check for `zero() == false` and call make_zero() if that holds; then execute the
872 * `reserve()` as planned. This is an intentional restriction forcing caller to explicitly allow a relatively slow
873 * reallocation op. You'll note a similar suggestion for the other reserve()-using methods/operators.
874 *
875 * Formally: If `capacity >= 1`, and `zero() == true`, then a buffer is allocated; and the internal ownership
876 * ref-count is set to 1.
877 *
878 * @param capacity
879 * Non-negative desired minimum capacity.
880 * @param logger_ptr
881 * The Logger implementation to use in *this* routine (synchronously) or asynchronously when TRACE-logging
882 * in the event of buffer dealloc. Null allowed.
883 */
884 void reserve(size_type capacity, log::Logger* logger_ptr = nullptr);
885
886 /**
887 * Identical to similar-sig overload except, if a `capacity`-sized buffer is allocated, then all `size` elements are
888 * performantly initialized to zero.
889 *
890 * Using this overload, instead of using the non-init one followed by your own explicit
891 * `memset(X.data(), 0, X.size())` (or similar), is likely to be significantly faster in at least some cases.
892 * It is *not* mere syntactic sugar.
893 *
894 * @see resize() and `size`-taking ctor also have Clear_on_alloc forms.
895 *
896 * @param coa_tag
897 * API-choosing tag util::CLEAR_ON_ALLOC.
898 * @param capacity
899 * See similar overload.
900 * @param logger_ptr
901 * See similar overload.
902 */
903 void reserve(size_type capacity, Clear_on_alloc coa_tag, log::Logger* logger_ptr = nullptr);
904
905 /**
906 * Guarantees post-condition `zero() == true` by dropping `*this` ownership of the allocated internal buffer if any;
907 * if no other Basic_blob holds ownership of that buffer, then that buffer is deallocated also. Recall that
908 * other `Basic_blob`s can only gain co-ownership via `share*()`; hence if one does not use that feature, make_zero()
909 * will in fact deallocate the buffer (if any).
910 *
911 * That post-condition can also be thought of as `*this` becoming indistinguishable from a default-constructed
912 * Basic_blob.
913 *
914 * Performance/behavior: Assuming zero() is not already `true`, this will deallocate capacity() sized buffer
915 * and save a null pointer.
916 *
917 * The many operations that involve reserve() in their doc headers will explain importance of this method:
918 * As a rule, no method except make_zero() allows one to request an ownership-drop or deallocation of the existing
919 * buffer, even if this would be necessary for a larger buffer to be allocated. Therefore, if you intentionally want
920 * to allow such an operation, you CAN, but then you MUST explicitly call make_zero() first.
921 *
922 * Formally: If `!zero()`, then the internal ownership ref-count is decremented by 1, and if it reaches
923 * 0, then a buffer is deallocated.
924 *
925 * @param logger_ptr
926 * The Logger implementation to use in *this* routine (synchronously) only. Null allowed.
927 */
928 void make_zero(log::Logger* logger_ptr = nullptr);
929
930 /**
931 * Guarantees post-condition `size() == size` and `start() == start`; no values in pre-call range `[begin(), end())`
932 * are changed; any values *added* to that range by the call are not initialized to zero or otherwise.
933 *
934 * From other invariants and behaviors described, you'll realize
935 * this essentially means `reserve(size + start)` followed by saving `size` and `start` into internal size members.
936 * The various implications of this can be deduced by reading the related methods' doc headers. The key is to
937 * understand how reserve() works, including what it disallows (growth in size of an existing buffer).
938 *
939 * Formally: If `size >= 1`, and `zero() == true`, then a buffer is allocated; and the internal ownership
940 * ref-count is set to 1.
941 *
942 * @see a similar overload that takes Clear_on_alloc tag arg, if you *do* want the elements to be zero-initialized.
943 * Doing so is often faster than your own explicit `memset(X.data(), 0, X.size())` (or similar).
944 *
945 * ### Leaving start() unmodified ###
946 * `start` is taken to be the value of arg `start_or_unchanged`; unless the latter is set to special value
947 * #S_UNCHANGED; in which case `start` is taken to equal start(). Since the default is indeed #S_UNCHANGED,
948 * the oft-encountered expression `resize(N)` will adjust only size() and leave start() unmodified -- often the
949 * desired behavior.
950 *
951 * @param size
952 * Non-negative desired value for size().
953 * @param start_or_unchanged
954 * Non-negative desired value for start(); or special value #S_UNCHANGED. See above.
955 * @param logger_ptr
956 * The Logger implementation to use in *this* routine (synchronously) or asynchronously when TRACE-logging
957 * in the event of buffer dealloc. Null allowed.
958 */
959 void resize(size_type size, size_type start_or_unchanged = S_UNCHANGED, log::Logger* logger_ptr = nullptr);
960
961 /**
962 * Identical to similar-sig overload except, if a `capacity`-sized buffer is allocated, then all `size` elements are
963 * performantly initialized to zero.
964 *
965 * Using this overload, instead of using the non-init one followed by your own explicit
966 * `memset(X.data(), 0, X.size())` (or similar), is likely to be significantly faster in at least some cases.
967 * It is *not* mere syntactic sugar.
968 *
969 * @see reserve() and `size`-taking ctor also have Clear_on_alloc forms.
970 *
971 * @param coa_tag
972 * API-choosing tag util::CLEAR_ON_ALLOC.
973 * @param size
974 * See similar overload.
975 * @param start_or_unchanged
976 * See similar overload.
977 * @param logger_ptr
978 * See similar overload.
979 */
981 size_type start_or_unchanged = S_UNCHANGED, log::Logger* logger_ptr = nullptr);
982
983 /**
984 * Restructures blob to consist of an internally allocated buffer and a `[begin(), end)` range starting at
985 * offset `prefix_size` within that buffer. More formally, it is a simple resize() wrapper that ensures
986 * the internally allocated buffer remains unchanged or, if none is currently large enough to
987 * store `prefix_size` elements, is allocated to be of size `prefix_size`; and that `start() == prefix_size`.
988 *
989 * All of resize()'s behavior, particularly any restrictions about capacity() growth, applies, so in particular
990 * remember you may need to first make_zero() if the internal buffer would need to be REallocated to satisfy the
991 * above requirements.
992 *
993 * In practice, with current reserve() (and thus resize()) restrictions -- which are intentional -- this method is
994 * most useful if you already have a Basic_blob with internally allocated buffer of size *at least*
995 * `n == size() + start()` (and `start() == 0` for simplicity), and you'd like to treat this buffer as containing
996 * no-longer-relevant prefix of length S (which becomes new value for start()) and have size() be readjusted down
997 * accordingly, while `start() + size() == n` remains unchaged. If the buffer also contains irrelevant data
998 * *past* a certain offset N, you can first make it irrelevant via `resize(N)` (then call `start_past_prefix(S)`
999 * as just described):
1000 *
1001 * ~~~
1002 * Basic_blob b;
1003 * // ...
1004 * // b now has start() == 0, size() == M.
1005 * // We want all elements outside [S, N] to be irrelevant, where S > 0, N < M.
1006 * // (E.g., first S are a frame prefix, while all bytes past N are a frame postfix, and we want just the frame
1007 * // without any reallocating or copying.)
1008 * b.resize(N);
1009 * b.start_past_prefix(S);
1010 * // Now, [b.begin(), b.end()) are the frame bytes, and no copying/allocation/deallocation has occurred.
1011 * ~~~
1012 *
1013 * @param prefix_size
1014 * Desired prefix length. `prefix_size == 0` is allowed and is a degenerate case equivalent to:
1015 * `resize(start() + size(), 0)`.
1016 */
1017 void start_past_prefix(size_type prefix_size);
1018
1019 /**
1020 * Like start_past_prefix() but shifts the *current* prefix position by the given *incremental* value
1021 * (positive or negative). Identical to `start_past_prefix(start() + prefix_size_inc)`.
1022 *
1023 * Behavior is undefined for negative `prefix_size_inc` whose magnitue exceeds start() (assertion may trip).
1024 *
1025 * Behavior is undefined in case of positive `prefix_size_inc` that results in overflow.
1026 *
1027 * @param prefix_size_inc
1028 * Positive or negative (or zero) increment, so that start() is changed to `start() + prefix_size_inc`.
1029 */
1031
1032 /**
1033 * Equivalent to `resize(0, start())`.
1034 *
1035 * Note that the value returned by start() will *not* change due to this call. Only size() (and the corresponding
1036 * internally stored datum) may change. If one desires to reset start(), use resize() directly (but if one
1037 * plans to work on a sub-`Basic_blob` of a shared pool -- see class doc header -- please think twice first).
1038 */
1039 void clear();
1040
1041 /**
1042 * Performs the minimal number of operations to make range `[begin(), end())` unchanged except for lacking
1043 * sub-range `[first, past_last)`.
1044 *
1045 * Performance/behavior: At most, this copies the range `[past_last, end())` to area starting at `first`;
1046 * and then adjusts internally stored size member.
1047 *
1048 * @param first
1049 * Pointer to first element to erase. It must be dereferenceable, or behavior is undefined (assertion may
1050 * trip). Corollary: invoking `erase()` when `empty() == true` is undefined behavior.
1051 * @param past_last
1052 * Pointer to one past the last element to erase. If `past_last <= first`, call is a no-op.
1053 * @return Iterator equal to `first`. (This matches standard expectation for container `erase()` return value:
1054 * iterator to element past the last one erased. In this contiguous sequence that simply equals `first`,
1055 * since everything starting with `past_last` slides left onto `first`. In particular:
1056 * If `past_last == this->end()` at entry, then the new end() is returned: everything starting with
1057 * `first` was erased and thus `first == end()` now. If nothing is erased `first` is still returned.)
1058 */
1060
1061 /**
1062 * Returns pointer to mutable first element; or end() if empty(). Null is a possible value in the latter case.
1063 *
1064 * @return Pointer, possibly null.
1065 */
1067
1068 /**
1069 * Returns pointer to immutable first element; or end() if empty(). Null is a possible value in the latter case.
1070 *
1071 * @return Pointer, possibly null.
1072 */
1074
1075 /**
1076 * Equivalent to const_begin().
1077 *
1078 * @return Pointer, possibly null.
1079 */
1081
1082 /**
1083 * Returns pointer one past mutable last element; empty() is possible. Null is a possible value in the latter case.
1084 *
1085 * @return Pointer, possibly null.
1086 */
1088
1089 /**
1090 * Returns pointer one past immutable last element; empty() is possible. Null is a possible value in the latter case.
1091 *
1092 * @return Pointer, possibly null.
1093 */
1095
1096 /**
1097 * Equivalent to const_end().
1098 *
1099 * @return Pointer, possibly null.
1100 */
1102
1103 /**
1104 * Returns reference to immutable first element. Behavior is undefined if empty().
1105 *
1106 * @return See above.
1107 */
1108 const value_type& const_front() const;
1109
1110 /**
1111 * Returns reference to immutable last element. Behavior is undefined if empty().
1112 *
1113 * @return See above.
1114 */
1115 const value_type& const_back() const;
1116
1117 /**
1118 * Equivalent to const_front().
1119 *
1120 * @return See above.
1121 */
1122 const value_type& front() const;
1123
1124 /**
1125 * Equivalent to const_back().
1126 *
1127 * @return See above.
1128 */
1129 const value_type& back() const;
1130
1131 /**
1132 * Returns reference to mutable first element. Behavior is undefined if empty().
1133 *
1134 * @return See above.
1135 */
1137
1138 /**
1139 * Returns reference to mutable last element. Behavior is undefined if empty().
1140 *
1141 * @return See above.
1142 */
1144
1145 /**
1146 * Equivalent to const_begin().
1147 *
1148 * @return Pointer, possibly null.
1149 */
1150 value_type const * const_data() const;
1151
1152 /**
1153 * Equivalent to begin().
1154 *
1155 * @return Pointer, possibly null.
1156 */
1158
1159 /**
1160 * Synonym of const_begin(). Exists as standard container method (hence the odd formatting).
1161 *
1162 * @return See const_begin().
1163 */
1165
1166 /**
1167 * Synonym of const_end(). Exists as standard container method (hence the odd formatting).
1168 *
1169 * @return See const_end().
1170 */
1172
1173 /**
1174 * Returns `true` if and only if: `this->derefable_iterator(it) || (it == this->const_end())`.
1175 *
1176 * @param it
1177 * Iterator/pointer to check.
1178 * @return See above.
1179 */
1181
1182 /**
1183 * Returns `true` if and only if the given iterator points to an element within this blob's size() elements.
1184 * In particular, this is always `false` if empty(); and also when `it == this->const_end()`.
1185 *
1186 * @param it
1187 * Iterator/pointer to check.
1188 * @return See above.
1189 */
1191
1192 /**
1193 * Convenience accessor returning an immutable boost.asio buffer "view" into the entirety of the blob.
1194 * Equivalent to `const_buffer(const_data(), size())`.
1195 *
1196 * @return See above.
1197 */
1198 boost::asio::const_buffer const_buffer() const;
1199
1200 /**
1201 * Same as const_buffer() but the returned view is mutable.
1202 * Equivalent to `mutable_buffer(data(), size())`.
1203 *
1204 * @return See above.
1205 */
1207
1208 /**
1209 * Returns a copy of the internally cached #Allocator as set by a constructor or assign() or
1210 * assignment-operator, whichever happened last.
1211 *
1212 * @return See above.
1213 */
1215
1216protected:
1217 // Constants.
1218
1219 /// Our flow::log::Component.
1221
1222 // Methods.
1223
1224 /**
1225 * Impl of share_after_split_equally() but capable of emitting not just `*this` type (`Basic_blob<...>`)
1226 * but any sub-class (such as `Blob`/`Sharing_blob`) provided a functor like share_after_split_left() but returning
1227 * an object of that appropriate type.
1228 *
1229 * @tparam Emit_blob_func
1230 * See share_after_split_equally(); however it is to take the type to emit which can
1231 * be `*this` Basic_blob or a sub-class.
1232 * @tparam Share_after_split_left_func
1233 * A callback with signature identical to share_after_split_left() but returning
1234 * the same type emitted by `Emit_blob_func`.
1235 * @param size
1236 * See share_after_split_equally().
1237 * @param headless_pool
1238 * See share_after_split_equally().
1239 * @param emit_blob_func
1240 * See `Emit_blob_func`.
1241 * @param logger_ptr
1242 * See share_after_split_equally().
1243 * @param share_after_split_left_func
1244 * See `Share_after_split_left_func`.
1245 */
1246 template<typename Emit_blob_func, typename Share_after_split_left_func>
1248 Emit_blob_func&& emit_blob_func,
1249 log::Logger* logger_ptr,
1250 Share_after_split_left_func&& share_after_split_left_func);
1251
1252private:
1253
1254 // Types.
1255
1256 /**
1257 * Internal deleter functor used if and only if #S_IS_VANILLA_ALLOC is `false` and therefore only with
1258 * #Buf_ptr being `boost::interprocess::shared_ptr` or
1259 * deleter-parameterized `unique_ptr`. Basically its task is to undo the
1260 * `alloc_raw().allocate()` call made when allocating a buffer in reserve(); the result of that call is
1261 * passed-to `shared_ptr::reset()` or `unique_ptr::reset()`; as is alloc_raw() (for any aux allocation needed,
1262 * but only for `shared_ptr` -- `unique_ptr` needs no aux data); as is
1263 * an instance of this Deleter_raw (to specifically dealloc the buffer when the ref-count reaches 0).
1264 * (In the `unique_ptr` case there is no ref-count per se; or one can think of it as a ref-count that equals 1.)
1265 *
1266 * Note that Deleter_raw is used only to dealloc the buffer actually controlled by the `shared_ptr` group
1267 * or `unique_ptr`. `shared_ptr` will use the #Allocator directly to dealloc aux data. (We guess Deleter_raw
1268 * is a separate argument to `shared_ptr` to support array deletion; `boost::interprocess:shared_ptr` does not
1269 * provide built-in support for `U[]` as the pointee type; but the deleter can do whatever it wants/needs.)
1270 *
1271 * Note: this is not used except with custom #Allocator. With `std::allocator` the usual default `delete[]`
1272 * behavior is fine.
1273 *
1274 * ### How to delete using it ###
1275 * `operator()()` gets invoked by smart-pointer machinery; the pointer to delete is passed to it as an arg.
1276 * So we need not memorize it ourselves.
1277 *
1278 * ### How to initialize ###
1279 * Before `operator()()` will work, it has to be loaded with the buffer size and allocator (both items needed
1280 * by that operator in addition to the pointer itself). There are 3 ways to do this:
1281 * - Construct via 2-arg ctor that takes those values. As usual the allocator object is to be copied (if it's
1282 * even stateful; else that's a no-op).
1283 * - Move-construct from already-initialized Deleter_raw.
1284 * - First, default-construct. Then, move-assign from an existing already-initialized other Deleter_raw.
1285 *
1286 * For ~brevity we won't fully enumerate who uses these and when; but mainly wanted to
1287 * place you attention on that last possibility. Basic_blob::reserve_impl(), in the non-sharing, non-vanilla-alloc
1288 * case, when it does need to allocate `buf_ptr()`, will exercise that use-case. E.g., Basic_blob
1289 * default-ctor would default-ct a Deleter_raw; then the next `reserve_impl()`
1290 * would 2-arg-ct Deleter_raw and then move-assign it onto the default-cted Deleter_raw.
1291 */
1293 {
1294 public:
1295 // Types.
1296
1297 /**
1298 * Short-hand for the allocator's pointer type, pointing to Basic_blob::value_type.
1299 * This may or may not simply be `value_type*`; in cases including SHM-storing allocators without
1300 * a constant cross-process vaddr scheme it needs to be a fancy-pointer type instead (e.g.,
1301 * `boost::interprocess::offset_ptr<value_type>`).
1302 */
1303 using Pointer_raw = typename std::allocator_traits<Allocator>::pointer;
1304
1305 /// For `boost::interprocess::shared_ptr` and `unique_ptr` compliance (hence the irregular capitalization).
1307
1308 // Constructors/destructor.
1309
1310 /**
1311 * Default ctor: Deleter must never be invoked to delete anything in this state; suitable only for a null
1312 * smart-pointer. Without this a `unique_ptr<..., Deleter_Raw>` cannot be default-cted.
1313 */
1314 Deleter_raw();
1315
1316 /**
1317 * Constructs deleter by memorizing the allocator (of zero size if #Allocator is stateless, usually)
1318 * used to allocate whatever shall be passed-to `operator()()`; and the size (in # of `value_type`s)
1319 * of the buffer allocated there. The latter is required, at least technically, because
1320 * `Allocator::deallocate()` requires the value count, equal to that when `allocate()` was called,
1321 * to be passed-in. Many allocators probably don't really need this, as array size is typically recorded
1322 * invisibly near the array itself, but formally this is not guaranteed for all allocators.
1323 *
1324 * @param alloc_raw_src
1325 * Allocator to copy and store.
1326 * @param buf_sz
1327 * See above.
1328 */
1329 explicit Deleter_raw(const Allocator& alloc_raw_src, size_type buf_sz);
1330
1331 /**
1332 * Move-construction which may be required when we are used in `unique_ptr`. This is equivalent to
1333 * default-construction followed by move-assignment. See move-assignment operator doc header regarding
1334 * why we are defining both of these.
1335 *
1336 * @param moved_src
1337 * Moved guy. For cleanliness it becomes as-if default-cted.
1338 */
1339 Deleter_raw(Deleter_raw&& moved_src);
1340
1341 /**
1342 * Copy-construction which may be required when we are used in `boost::interprocess::shared_ptr` which
1343 * as of this writing requires copyable deleter in its `.reset()` and other places.
1344 *
1345 * @param src
1346 * Copied guy.
1347 */
1349
1350 // Methods.
1351
1352 /**
1353 * Move-assignment which is required when we are used in `unique_ptr`. User might invoke move-construction
1354 * or move-assignment of the Basic_blob; this reduces to Basic_blob::assign() (move overload); which will
1355 * do a swap -- that ultimately will move the stored Deleter_raw up to a few times.
1356 *
1357 * As of this writing we also manually overwrite `.get_deleter()` in one case in Basic_blob::reserve_impl();
1358 * so this is useful for that too.
1359 *
1360 * @param moved_src
1361 * Moved guy. For cleanliness it becomes as-if default-cted (unless it is the same object as `*this`).
1362 * @return `*this`.
1363 */
1364 Deleter_raw& operator=(Deleter_raw&& moved_src);
1365
1366 /**
1367 * Copy-assignment which is required when we are used in `boost::interprocess::shared_ptr` which
1368 * as of this writing requires copyable deleter in its `.reset()` and other places.
1369 *
1370 * @param src
1371 * Copied guy.
1372 * @return `*this`.
1373 */
1374 Deleter_raw& operator=(const Deleter_raw& src);
1375
1376 /**
1377 * Deallocates using `Allocator::deallocate()`, passing-in the supplied pointer (to `value_type`) `to_delete`
1378 * and the number of `value_type`s to delete (from ctor).
1379 *
1380 * @param to_delete
1381 * Pointer to buffer to delete as supplied by `shared_ptr` or `unique_ptr` internals.
1382 */
1383 void operator()(Pointer_raw to_delete);
1384
1385 private:
1386 // Data.
1387
1388 /**
1389 * See ctor: the allocator that `operator()()` shall use to deallocate. For stateless allocators this
1390 * typically has size zero.
1391 *
1392 * ### What's with `optional<>`? ###
1393 * ...Okay, so actually this has size (whatever `optional` adds, probably a `bool`) + `sizeof(Allocator)`,
1394 * the latter being indeed zero for stateless allocators. Why use `optional<>` though? Two reasons at least:
1395 * - Stateful allocators often cannot be default-cted; and our own default ctor requires that
1396 * #m_alloc_raw is initialized to *something*... even though it (by default ctor contract) will never be
1397 * accessed via `operator()()` in that form. Bottom line is a null smart-pointer needs a default-cted `*this`
1398 * for at least some smart-pointer types (namely `unique_ptr` at least; probably not `shared_ptr`).
1399 * - Yay, `optional` can be uninitialized.
1400 * - Some allocators (such as `boost::interprocess::allocator`) are not assignable, only
1401 * copy-constructible, while we need to be move-assignable (see doc header for move-assignment; spoiler alert:
1402 * Basic_blob::reserve_impl() may need that).
1403 * - Yay, `optional<T>` has `.emplace()` which will construct (including copy-construct) a `T`.
1404 *
1405 * It is slightly annoying that we waste the extra space for `optional` internals even when `Allocator`
1406 * is stateless (and it is often stateless!). Plus, when #Buf_ptr is `shared_ptr` instead of `unique_ptr`
1407 * these bullet points probably do not apply. Probably some meta-programming thing could be done to avoid even this
1408 * overhead, but in my (ygoldfel) opinion the overhead is so minor, it does not even rise to the level of a to-do.
1409 */
1410 std::optional<Allocator> m_alloc_raw;
1411
1412 /// See ctor and `operator()()`: the size of the buffer to deallocate.
1414 }; // class Deleter_raw
1415
1416 /**
1417 * The smart-pointer type used for buf_ptr(); a custom-allocator-and-SHM-friendly impl and parameterization is
1418 * used if necessary; otherwise a more typical concrete type is used.
1419 *
1420 * The following discussion assumes the more complex case wherein #S_SHARING is `true`. We discuss the simpler
1421 * converse case below that.
1422 *
1423 * Two things affect how buf_ptr() shall behave:
1424 * - Which type this resolves-to depending on #S_IS_VANILLA_ALLOC (ultimately #Allocator). This affects
1425 * many key things but most relevantly how it is dereferenced. Namely:
1426 * - Typical `shared_ptr` (used with vanilla allocator) will internally store simply a raw `value_type*`
1427 * and dereference trivially. This, however, will not work with some custom allocators, particularly
1428 * SHM-heap ones (without a constant cross-process vaddr scheme), wherein a raw `T*` meaningful in
1429 * the original process is meaningless in another.
1430 * - `boost::shared_ptr` and `std::shared_ptr` both have custom-allocator support via
1431 * `allocate_shared()` and co. However, as of this writing, they are not SHM-friendly; or another
1432 * way of putting it is they don't support custom allocators fully: `Allocator::pointer` is ignored;
1433 * it is assumed to essentially be raw `value_type*`, in that the `shared_ptr` internally stores
1434 * a raw pointer. boost.interprocess refers to this as the impetus for implementing the following:
1435 * - `boost::interprocess::shared_ptr` (used with custom allocator) will internally store an
1436 * instance of `Allocator::pointer` (to `value_type`) instead. To dereference it, its operators
1437 * such as `*` and `->` (etc.) will execute to properly translate to a raw `T*`.
1438 * The aforementioned `pointer` may simply be `value_type*` again; in which case there is no difference
1439 * to the standard `shared_ptr` situation; but it can instead be a fancy-pointer (actual technical term, yes,
1440 * in cppreference.com et al), in which case some custom code will run to translate some internal
1441 * data members (which have process-agnostic values) inside the fancy-pointer to a raw `T*`.
1442 * For example `boost::interprocess::offset_ptr<value_type>` does this by adding a stored offset to its
1443 * own `this`.
1444 * - How it is reset to a newly allocated buffer in reserve() (when needed).
1445 * - Typical `shared_ptr` is efficiently assigned using a `make_shared()` variant. However, here we store
1446 * a pointer to an array, not a single value (hence `<value_type[]>`); and we specifically want to avoid
1447 * any 0-initialization of the elements (per one of Basic_blob's promises). See reserve() which uses a
1448 * `make_shared()` variant that accomplishes all this.
1449 * - `boost::interprocess::shared_ptr` is reset differently due to a couple of restrictions, as it is made
1450 * to be usable in SHM (SHared Memory), specifically, plus it seems to refrain from tacking on every
1451 * normal `shared_ptr` feature. To wit: 1, `virtual` cannot be used; therefore the deleter type must be
1452 * declared at compile-time. 2, it has no special support for a native-array element-type (`value_type[]`).
1453 * Therefore it leaves that part up to the user: the buffer must be pre-allocated by the user
1454 * (and passed to `.reset()`); there is no `make_shared()` equivalent (which also means somewhat lower
1455 * perf, as aux data and user buffer are separately allocated and stored). Accordingly deletion is left
1456 * to the user, as there is no default deleter; one must be supplied. Thus:
1457 * - See reserve(); it calls `.reset()` as explained here, including using alloc_raw() to pre-allocate.
1458 * - See Deleter_raw, the deleter functor type an instance of which is saved by the `shared_ptr` to
1459 * invoke when ref-count reaches 0.
1460 *
1461 * Other than that, it's a `shared_ptr`; it works as usual.
1462 *
1463 * ### Why use typical `shared_ptr` at all? Won't the fancy-allocator-supporting one work for the vanilla case? ###
1464 * Yes, it would work. And there would be less code without this dichotomy (although the differences are,
1465 * per above, local to this alias definition; and reserve() where it allocates buffer). There are however reasons
1466 * why typical `shared_ptr` (we choose `boost::shared_ptr` over `std::shared_ptr`; that discussion is elsewhere,
1467 * but basically `boost::shared_ptr` is solid and full-featured/mature, though either choice would've worked).
1468 * They are at least:
1469 * - It is much more frequently used, preceding and anticipating its acceptance into the STL standard, so
1470 * maturity and performance are likelier.
1471 * - Specifically it supports a perf-enhancing use mode: using `make_shared()` (and similar) instead of
1472 * `.reset(<raw ptr>)` (or similar ctor) replaces 2 allocs (1 for user data, 1 for aux data/ref-count)
1473 * with 1 (for both).
1474 * - If verbose logging in the deleter is desired, its `virtual`-based type-erased deleter semantics make that
1475 * quite easy to achieve.
1476 *
1477 * ### The case where #S_SHARING is `false` ###
1478 * Firstly: if so then the method -- share() -- that would *ever* increment `Buf_ptr::use_count()` beyond 1
1479 * is simply not compiled. Therefore using any type of `shared_ptr` is a waste of RAM (on the ref-count)
1480 * and cycles (on aux memory allocation and ref-count math), albeit a minor one. Hence we use `unique_ptr`
1481 * in that case instead. Even so, the above #S_IS_VANILLA_ALLOC dichotomy still applies but is quite a bit
1482 * simpler to handle; it's a degenerate case in a way.
1483 * - Typical `unique_ptr` already stores `Deleter::pointer` instead of `value_ptr*`. Therefore
1484 * We can use it for both cases; in the vanilla case supplying no `Deleter` template param
1485 * (the default `Deleter` has `pointer = value_ptr*`); otherwise supplying Deleter_raw whose
1486 * Deleter_raw::pointer comes from `Allocator::pointer`. This also, same as with
1487 * `boost::interprocess::shared_ptr`, takes care of the dealloc upon being nullified or destroyed.
1488 * - As for initialization:
1489 * - With #S_IS_VANILLA_ALLOC at `true`: Similarly to using a special array-friendly `make_shared()` variant,
1490 * we use a special array-friendly `make_unique()` variant.
1491 * - Otherwise: As with `boost::interprocess::shared_ptr` we cannot `make_*()` -- though AFAIK without
1492 * any perf penalty (there is no aux data) -- but reserve() must be quite careful to also
1493 * replace `buf_ptr()`'s deleter (which `.reset()` does not do... while `boost::interprocess::shared_ptr`
1494 * does).
1495 */
1496 using Buf_ptr = std::conditional_t<S_IS_VANILLA_ALLOC,
1497 std::conditional_t<S_SHARING,
1498 boost::shared_ptr<value_type[]>,
1499 boost::movelib::unique_ptr<value_type[]>>,
1500 std::conditional_t<S_SHARING,
1501 boost::interprocess::shared_ptr
1503 boost::movelib::unique_ptr<value_type, Deleter_raw>>>;
1504
1505 // Methods.
1506
1507 /**
1508 * For convenience/expressiveness, the pointer-to-main-buf for `*this`. The actual datum is inside a
1509 * `compressed_pair` for reasons explained elsewhere, and it is annoying/ugly having to specify that detail
1510 * all over the code. So one can just use this reference-returning accessor.
1511 *
1512 * ### Documentation for the datum referred-to by the return value ###
1513 *
1514 * Pointer to currently allocated buffer of size #m_capacity; null if and only if `zero() == true`.
1515 * Buffer is auto-freed at destruction; or in make_zero(); but only if by that point any share()-generated
1516 * other `Basic_blob`s have done the same. Otherwise the ref-count is merely decremented.
1517 * In the case of #S_SHARING being `false`, one can think of this ref-count as being always at most 1;
1518 * since share() is not compiled, and as a corollary a `unique_ptr` is used to avoid perf costs.
1519 * Thus make_zero() and dtor always dealloc in that case.
1520 *
1521 * For performance, we never initialize the values in the array to zeroes or otherwise.
1522 * This contrasts with `vector` and most other standard or Boost containers which use an `allocator` to
1523 * allocate any internal buffer, and most allocators default-construct (which means assign 0 in case of `uint8_t`)
1524 * any elements within allocated buffers, immediately upon the actual allocation on heap. As noted in doc header,
1525 * this behavior is surprisingly difficult to avoid (involving custom allocators and such).
1526 *
1527 * @return See above.
1528 */
1530
1531 /**
1532 * Ref-to-immutable counterpart to the other overload.
1533 * @return See above.
1534 */
1535 const Buf_ptr& buf_ptr() const;
1536
1537 /**
1538 * For convenience/expressiveness, the allocator object for `*this`. The actual datum is inside a
1539 * `compressed_pair` for reasons explained elsewhere, and it is annoying/ugly having to specify that detail
1540 * all over the code. So one can just use this reference-returning accessor.
1541 *
1542 * ### Documentation for the datum referred-to by the return value ###
1543 *
1544 * Copy of the allocator supplied by the user (though, if #Allocator is stateless,
1545 * it is typically defaulted to `Allocator{}`), as set by a constructor or assign() or
1546 * assignment-operator, whichever happened last. Used exclusively when allocating and deallocating
1547 * buf_ptr() in the *next* reserve() (potentially).
1548 *
1549 * By the rules of `Allocator_aware_container` (see cppreference.com):
1550 * - If `*this` is move-cted: datum move-cted from source datum counterpart.
1551 * - If `*this` is move-assigned: datum move-assigned from source datum counterpart if
1552 * `std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value == true` (else untouched).
1553 * - If `*this` is copy-cted: datum set to
1554 * `std::allocator_traits<Allocator>::select_on_container_copy_construction()` (pass-in source datum
1555 * counterpart).
1556 * - If `*this` is copy-assigned: datum copy-assigned if
1557 * `std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value == true` (else untouched).
1558 * - If `*this` is `swap()`ed: datum ADL-`swap()`ed with source datum counterpart if
1559 * `std::allocator_traits<Allocator>::propagate_on_container_swap::value == true` (else untouched).
1560 * - Otherwise this is supplied via a non-copy/move ctor arg by user.
1561 *
1562 * ### Specially treated value ###
1563 * If #Allocator is `std::allocator<value_type>` (as supposed to `something_else<value_type>`), then
1564 * this datum (while guaranteed set to the zero-sized copy of `std::allocator<value_type>()`) is never
1565 * in practice touched (outside of the above-mentioned moves/copies/swaps, though they also do nothing in reality
1566 * for this stateless allocator). This value by definition means we are to allocate on the regular heap;
1567 * and as of this writing for perf/other reasons we choose to use a vanilla
1568 * `*_ptr` with its default alloc-dealloc APIs (which perform `new[]`-`delete[]` respectively); we do not pass-in
1569 * alloc_raw() anywhere. See #Buf_ptr doc header for more. If we did pass it in to
1570 * `allocate_shared*()` or `boost::interprocess::shared_ptr::reset` the end result would be functionally
1571 * the same (`std::allocator::[de]allocate()` would get called; these call `new[]`/`delete[]`).
1572 *
1573 * ### Relationship between this datum and the allocator/deleter in `buf_ptr()` ###
1574 * (This is only applicable if #S_IS_VANILLA_ALLOC is `false`.)
1575 * buf_ptr() caches this datum internally in its centrally linked data. Ordinarily, then, they compare as equal.
1576 * In the corner case where (1) move-assign or copy-assign or swap() was used on `*this`, *and*
1577 * (2) #Allocator is stateful and *can* compare unequal (e.g., `boost::interprocess::allocator`):
1578 * they may come to compare as unequal. It is, however, not (in our case) particularly important:
1579 * this datum affects the *next* reserve() (potentially); the thing stored in buf_ptr() affects the logic when
1580 * the underlying buffer is next deallocated. The two don't depend on each other.
1581 *
1582 * @return See above.
1583 */
1585
1586 /**
1587 * Ref-to-immutable counterpart to the other overload.
1588 * @return See above.
1589 */
1590 const Allocator& alloc_raw() const;
1591
1592 /**
1593 * Implements reserve() overloads.
1594 *
1595 * @param clear_on_alloc
1596 * Whether the Clear_on_alloc overload or the other one was called.
1597 * @param capacity
1598 * See reserve().
1599 * @param logger_ptr
1600 * See reserve().
1601 */
1602 void reserve_impl(size_type capacity, bool clear_on_alloc, log::Logger* logger_ptr);
1603
1604 /**
1605 * Implements resize() overloads.
1606 *
1607 * @param clear_on_alloc
1608 * Whether the Clear_on_alloc overload or the other one was called.
1609 * @param size
1610 * See resize().
1611 * @param start_or_unchanged
1612 * See resize().
1613 * @param logger_ptr
1614 * See resize().
1615 */
1616 void resize_impl(size_type size, bool clear_on_alloc, size_type start_or_unchanged, log::Logger* logger_ptr);
1617
1618 /**
1619 * The body of swap(), except for the part that swaps (or decides not to swap) alloc_raw(). As of this writing
1620 * used by swap() and assign() (move overload) which perform mutually different steps w/r/t alloc_raw().
1621 *
1622 * @param other
1623 * See swap().
1624 * @param logger_ptr
1625 * See swap().
1626 */
1627 void swap_impl(Basic_blob& other, log::Logger* logger_ptr) noexcept;
1628
1629 /**
1630 * Returns iterator-to-mutable equivalent to given iterator-to-immutable.
1631 *
1632 * @param it
1633 * Self-explanatory. No assumptions are made about valid_iterator() or derefable_iterator() status.
1634 * @return Iterator to same location as `it`.
1635 */
1637
1638 // Data.
1639
1640 /**
1641 * Combined -- to enable empty base-class optimization (EBO) -- storage for the two data items, refs to which are
1642 * returned by alloc_raw() and buf_ptr() respectively.
1643 *
1644 * @see alloc_raw() and buf_ptr() doc headers for actual documentation for these two important items (especially
1645 * buf_ptr()).
1646 *
1647 * ### Rationale ###
1648 * Please look into EBO to grok this. That aside -- just think of `alloc_raw()` as essentially an
1649 * `m_alloc_raw` data member, `buf_ptr()` as an `m_buf_ptr` data member. They are only stored in this pair thingie
1650 * due to an obscure, but perf-affecting, C++ technicality. The aforementioned ref-returning accessors avoid having
1651 * to write `m_alloc_and_buf_ptr.second` and `.first` all over the place.
1652 */
1653 boost::compressed_pair<Allocator, Buf_ptr> m_alloc_and_buf_ptr;
1654
1655 /// See capacity(); but #m_capacity is meaningless (and containing unknown value) if `!buf_ptr()` (i.e., zero()).
1657
1658 /// See start(); but #m_start is meaningless (and containing unknown value) if `!buf_ptr()` (i.e., zero()).
1660
1661 /// See size(); but #m_size is meaningless (and containing unknown value) if `!buf_ptr()` (i.e., zero()).
1663}; // class Basic_blob
1664
1665// Free functions: in *_fwd.hpp.
1666
1667// Template implementations.
1668
1669// buf_ptr() initialized to null pointer. n_capacity and m_size remain uninit (meaningless until buf_ptr() changes).
1670template<typename Allocator_t, bool SHARING>
1672 m_alloc_and_buf_ptr(alloc_raw_src), // Copy allocator; stateless alloc should have size 0 (no-op for the processor).
1673 m_capacity(0), // Not necessary, but some compilers will warn in some situations. Fine; it's cheap enough.
1674 m_start(0), // Ditto.
1675 m_size(0) // Ditto.
1676{
1677 // OK.
1678}
1679
1680template<typename Allocator_t, bool SHARING>
1682 (size_type size, log::Logger* logger_ptr, const Allocator& alloc_raw_src) :
1683
1684 Basic_blob(alloc_raw_src) // Delegate.
1685{
1686 resize(size, 0, logger_ptr);
1687}
1688
1689template<typename Allocator_t, bool SHARING>
1691 (size_type size, Clear_on_alloc coa_tag, log::Logger* logger_ptr, const Allocator& alloc_raw_src) :
1692
1693 Basic_blob(alloc_raw_src) // Delegate.
1694{
1695 resize(size, coa_tag, 0, logger_ptr);
1696}
1697
1698template<typename Allocator_t, bool SHARING>
1700 // Follow rules established in alloc_raw() doc header. This is compatible with the delegated-to ctor.
1701 Basic_blob(std::allocator_traits<Allocator>::select_on_container_copy_construction(src.alloc_raw()))
1702{
1703 /* What we want to do here, ignoring allocators, is (for concision): `assign(src, logger_ptr);`
1704 * However copy-assignment also must do something different w/r/t alloc_raw() than what we had to do above
1705 * (again see alloc_raw() doc header); so just copy/paste the rest of what operator=(copy) would do.
1706 * Skipping most comments therein, as they don't much apply in our case. Code reuse level is all-right;
1707 * and we can skip the `if` from assign(). */
1708 assign_copy(src.const_buffer(), logger_ptr);
1709}
1710
1711template<typename Allocator_t, bool SHARING>
1713 // Follow rules established in alloc_raw() doc header:
1714 m_alloc_and_buf_ptr(std::move(moved_src.alloc_raw())),
1715 m_capacity(0), // See comment in first delegated ctor above.
1716 m_start(0), // Ditto.
1717 m_size(0) // Ditto
1718{
1719 /* Similar to copy ctor above, do the equivalent of assign(move(moved_src), logger_ptr) minus the allocator work.
1720 * That reduces to simply: */
1721 swap_impl(moved_src, logger_ptr);
1722}
1723
1724template<typename Allocator_t, bool SHARING>
1726
1727template<typename Allocator_t, bool SHARING>
1730{
1731 if (this != &src)
1732 {
1733 // Take care of the "Sharing blobs" corner case from our doc header. The rationale for this is pointed out there.
1734 if constexpr(S_SHARING)
1735 {
1736 assert(!blobs_sharing(*this, src));
1737 }
1738
1739 // For alloc_raw(): Follow rules established in alloc_raw() doc header.
1740 if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value)
1741 {
1742 alloc_raw() = src.alloc_raw(); // No copy-assignment for some allocators, but then p_o_c_c_a would be false.
1743 }
1744 // else: Leave alloc_raw() alone.
1745
1746 /* Either way: for stateful (not-always-equal) allocators: the allocator used to dealloc buf_ptr() (if
1747 * buf_ptr() not null) is cached inside buf_ptr(). New alloc_raw(), even if it was changed, is relevant only to
1748 * the future allocation(s) if any. */
1749
1750 // Now to the relatively uncontroversial stuff. To copy the rest we'll just do:
1751
1752 /* resize(N, 0); copy over N bytes. Note that it disallows `N > capacity()` unless zero(), but they can explicitly
1753 * make_zero() before calling us, if they are explicitly OK with the performance cost of the reallocation that will
1754 * trigger. This is all as advertised; and it satisfies the top priority listed just below. */
1755 assign_copy(src.const_buffer(), logger_ptr);
1756
1757 /* ^-- Corner case: Suppose src.size() == 0. The above then reduces to: if (!zero()) { m_size = m_start = 0; }
1758 * (Look inside its source code; you'll see.)
1759 *
1760 * We guarantee certain specific behavior in doc header, and below implements that.
1761 * We will indicate how it does so; but ALSO why those promises are made in the first place (rationale).
1762 *
1763 * In fact, we will proceed under the following priorities, highest to lowest:
1764 * - User CAN switch order of our priorities sufficiently easily.
1765 * - Be as fast as possible, excluding minimizing constant-time operations such as scalar assignments.
1766 * - Use as little memory in *this as possible.
1767 *
1768 * We will NOT attempt to make *this have the same internal structure as src as its own independent virtue.
1769 * That doesn't seem useful and would make things more difficult obviously. Now:
1770 *
1771 * Either src.zero(), or not; but regardless src.size() == 0. Our options are essentially these:
1772 * make_zero(); or resize(0, 0). (We could also perhaps copy src.buf_ptr()[] and then adjust m_size = 0, but
1773 * this is clearly slower and only gains the thing we specifically pointed out is not a virtue above.)
1774 *
1775 * Let's break down those 2 courses of action, by situation, then:
1776 * - zero() && src.zero(): make_zero() and resize(0, 0) are equivalent; so nothing to decide. Either would be fine.
1777 * - zero() && !src.zero(): Ditto.
1778 * - !zero() && !src.zero(): make_zero() is slower than resize(0, 0); and moreover the latter may mean faster
1779 * operations subsequently, if they subsequently choose to reserve(N) (including resize(N), etc.) to
1780 * N <= capacity(). So resize(0, 0) wins according to the priority order listed above.
1781 * - !zero() && src.zero(): Ditto.
1782 *
1783 * So then we decided: resize(0, 0). And, indeed, resize(0, 0) is equivalent to the above snippet.
1784 * So, we're good. */
1785 } // if (this != &src)
1786
1787 return *this;
1788} // Basic_blob::assign(copy)
1789
1790template<typename Allocator_t, bool SHARING>
1792{
1793 return assign(src);
1794}
1795
1796template<typename Allocator_t, bool SHARING>
1799{
1800 if (this != &moved_src)
1801 {
1802 // For alloc_raw(): Follow rules established in alloc_raw() doc header.
1803 if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value)
1804 {
1805 alloc_raw() = std::move(moved_src.alloc_raw()); // Similar comment here as for assign() copy overload.
1806 }
1807 // else: Leave alloc_raw() alone.
1808
1809 /* Either way: for stateful (not-always-equal) allocators: the allocator used to dealloc buf_ptr() (if
1810 * moved_src.buf_ptr() not null) is cached inside moved_src.buf_ptr() and will be swap_impl()ed into
1811 * our buf_ptr() as part of the deleter. New alloc_raw(), even if it was changed, is relevant only to
1812 * the future allocation(s) if any. */
1813
1814 // Now to the relatively uncontroversial stuff.
1815
1816 make_zero(logger_ptr); // Spoiler alert: it's: if (!zero()) { buf_ptr().reset(); }
1817 // So now buf_ptr() is null; hence the other m_* (other than alloc_raw()) are meaningless.
1818
1819 swap_impl(moved_src, logger_ptr);
1820 /* Now *this is equal to old moved_src; new moved_src is valid and zero(); and nothing was copied -- as advertised.
1821 * swap_impl() does not touch alloc_raw() or moved_src.alloc_raw(), and we handled that already. */
1822 } // if (this != &moved_src)
1823
1824 return *this;
1825} // Basic_blob::assign(move)
1826
1827template<typename Allocator_t, bool SHARING>
1830{
1831 return assign(std::move(moved_src));
1832}
1833
1834template<typename Allocator_t, bool SHARING>
1836{
1837 using std::swap;
1838
1839 /* As of this writing move-ct and move-assign both use us as the core of what needs to happen; so the below code
1840 * has a particularly high responsibility of correctness and performance. */
1841
1842 if (this != &other)
1843 {
1844 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
1845 {
1846 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
1847 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] (internal buffer sized [" << capacity() << "]) "
1848 "swapping <=> Blob [" << &other << "] (internal buffer sized "
1849 "[" << other.capacity() << "]).");
1850 }
1851
1852 /* The following looks simple, and that's great. Just realize that buf_ptr() refers to a smart-pointer of one
1853 * of several types, and (see reserve_impl()) a custom deleter of type Deleter_raw may be stored therein. When
1854 * the smart-pointer is of a shared_ptr<> variety, that doesn't complicate anything, probably, as the deleter is
1855 * in the control block, so a swap just swaps a pair of ctl-block pointers, and that's that. However when it is
1856 * a unique_ptr, then Deleter_raw has to be move-assignable and probably
1857 * move-ctible; the swap (*) will then do a move-ct of a Deleter_raw followed by 2 move-assigns of them (probably).
1858 * So, as of this writing, we specifically made Deleter_raw move-ctible and move-assignable carefully; and
1859 * disabled any copy-ct or copy-assign for cleanliness and determinism.
1860 *
1861 * (*) We could make a swap(Deleter_raw&, Deleter_raw&) as well, which would be directly invoked via ADL-lookup
1862 * in the following statement; but let's leave well-enough alone and leave it as std::swap() and move-ct/assigns.
1863 * As of this writing reserve_impl() uses Deleter_raw move-assignment also, anyway. */
1864 swap(buf_ptr(), other.buf_ptr());
1865
1866 /* Some compilers in some build configs issue maybe-uninitialized warning here, when `other` is as-if
1867 * default-cted (hence the following three are intentionally uninitialized), particularly with heavy
1868 * auto-inlining by the optimizer. False positive in our case, and in Blob-land we try not to give away perf
1869 * at all so: */
1870#pragma GCC diagnostic push
1871#pragma GCC diagnostic ignored "-Wpragmas" // For older versions, where the following does not exist/cannot be disabled.
1872#pragma GCC diagnostic ignored "-Wunknown-warning-option" // (Similarly for clang.)
1873#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
1874
1875 swap(m_capacity, other.m_capacity); // Meaningless if zero() but harmless.
1876 swap(m_size, other.m_size); // Ditto.
1877 swap(m_start, other.m_start); // Ditto.
1878
1879#pragma GCC diagnostic pop
1880
1881 /* Skip alloc_raw(): swap() has to do it by itself; we are called from it + move-assign/ctor which require
1882 * mutually different treatment for alloc_raw(). */
1883 }
1884} // Basic_blob::swap_impl()
1885
1886template<typename Allocator_t, bool SHARING>
1888{
1889 using std::swap;
1890
1891 // For alloc_raw(): Follow rules established in m_alloc_and_buf_ptr doc header.
1892 if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value)
1893 {
1894 if (&alloc_raw() != &other.alloc_raw()) // @todo Is this redundant? Or otherwise unnecessary?
1895 {
1896 swap(alloc_raw(), other.alloc_raw());
1897 }
1898 }
1899 /* else: Leave both `alloc_raw()`s alone. What does it mean though? Well, see either assign(); the same
1900 * theme applies here: Each buf_ptr()'s cached allocator/deleter will potentially not equal its respective
1901 * alloc_raw() anymore; but the latter affects only the *next* allocating reserve(); so it is fine.
1902 * That said, to quote cppreference.com: "Note: swapping two containers with unequal allocators if
1903 * propagate_on_container_swap is false is undefined behavior." So, while it will work for us, trying such
1904 * a swap() would be illegal user behavior in any case. */
1905
1906 // Now to the relatively uncontroversial stuff.
1907 swap_impl(other, logger_ptr);
1908}
1909
1910template<typename Allocator_t, bool SHARING>
1912 Basic_blob<Allocator_t, SHARING>& blob2, log::Logger* logger_ptr) noexcept
1913{
1914 return blob1.swap(blob2, logger_ptr);
1915}
1916
1917template<typename Allocator_t, bool SHARING>
1919{
1920 static_assert(S_SHARING,
1921 "Do not invoke (and thus instantiate) share() or derived methods unless you set the SHARING "
1922 "template parameter to true. Sharing will be enabled at a small perf cost; see class doc header.");
1923 // Note: The guys that call it will cause the same check to occur, since instantiating them will instantiate us.
1924
1925 assert(!zero()); // As advertised.
1926
1927 Basic_blob sharing_blob{alloc_raw(), logger_ptr}; // Null Basic_blob (let that ctor log via same Logger if any).
1928 assert(!sharing_blob.buf_ptr());
1929 sharing_blob.buf_ptr() = buf_ptr();
1930 // These are currently (as of this writing) uninitialized (possibly garbage).
1931 sharing_blob.m_capacity = m_capacity;
1932 sharing_blob.m_start = m_start;
1933 sharing_blob.m_size = m_size;
1934
1935 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
1936 {
1937 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
1939 ("Blob [" << this << "] shared with new Blob [" << &sharing_blob << "]; ref-count incremented.");
1940 }
1941
1942 return sharing_blob;
1943}
1944
1945template<typename Allocator_t, bool SHARING>
1948{
1949 if (lt_size > size())
1950 {
1951 lt_size = size();
1952 }
1953
1954 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
1955 {
1956 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
1957 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] shall be shared with new Blob, splitting off the first "
1958 "[" << lt_size << "] values into that one and leaving the remaining "
1959 "[" << (size() - lt_size) << "] in this one.");
1960 }
1961
1962 auto sharing_blob = share(logger_ptr); // sharing_blob sub-Basic_blob is equal to *this sub-Basic_blob. Adjust:
1963 sharing_blob.resize(lt_size); // Note: sharing_blob.start() remains unchanged.
1964 start_past_prefix_inc(lt_size);
1965
1966 return sharing_blob;
1967}
1968
1969template<typename Allocator_t, bool SHARING>
1972{
1973 if (rt_size > size())
1974 {
1975 rt_size = size();
1976 }
1977
1978 const auto lt_size = size() - rt_size;
1979 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
1980 {
1981 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
1982 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] shall be shared with new Blob, splitting off "
1983 "the last [" << rt_size << "] values into that one and leaving the "
1984 "remaining [" << lt_size << "] in this one.");
1985 }
1986
1987 auto sharing_blob = share(logger_ptr); // sharing_blob sub-Basic_blob is equal to *this sub-Basic_blob. Adjust:
1988 resize(lt_size); // Note: start() remains unchanged.
1989 sharing_blob.start_past_prefix_inc(lt_size);
1990
1991 return sharing_blob;
1992}
1993
1994template<typename Allocator_t, bool SHARING>
1995template<typename Emit_blob_func, typename Share_after_split_left_func>
1997 (size_type size, bool headless_pool, Emit_blob_func&& emit_blob_func, log::Logger* logger_ptr,
1998 Share_after_split_left_func&& share_after_split_left_func)
1999{
2000 assert(size != 0);
2001 assert(!empty());
2002
2003 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
2004 {
2005 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
2006 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] of size [" << this->size() << "] shall be split into "
2007 "adjacent sharing sub-Blobs of size [" << size << "] each "
2008 "(last one possibly smaller).");
2009 }
2010
2011 do
2012 {
2013 emit_blob_func(share_after_split_left_func(size, logger_ptr)); // share_after_split_left_func() logs plenty.
2014 }
2015 while (!empty());
2016
2017 if (headless_pool)
2018 {
2019 make_zero(logger_ptr);
2020 }
2021} // Basic_blob::share_after_split_equally_impl()
2022
2023template<typename Allocator_t, bool SHARING>
2024template<typename Emit_blob_func>
2026 Emit_blob_func&& emit_blob_func,
2027 log::Logger* logger_ptr)
2028{
2029 share_after_split_equally_impl(size, headless_pool, std::move(emit_blob_func), logger_ptr,
2030 [this](size_type lt_size, log::Logger* logger_ptr) -> Basic_blob
2031 {
2032 return share_after_split_left(lt_size, logger_ptr);
2033 });
2034}
2035
2036template<typename Allocator_t, bool SHARING>
2037template<typename Blob_container>
2039 (size_type size, bool headless_pool, Blob_container* out_blobs_ptr, log::Logger* logger_ptr)
2040{
2041 // If changing this please see Blob_with_log_context::<same method>().
2042
2043 assert(out_blobs_ptr);
2044 share_after_split_equally(size, headless_pool, [&](Basic_blob&& blob_moved)
2045 {
2046 out_blobs_ptr->push_back(std::move(blob_moved));
2047 }, logger_ptr);
2048}
2049
2050template<typename Allocator_t, bool SHARING>
2051template<typename Blob_ptr_container>
2053 bool headless_pool,
2054 Blob_ptr_container* out_blobs_ptr,
2055 log::Logger* logger_ptr)
2056{
2057 // If changing this please see Blob_with_log_context::<same method>().
2058
2059 // By documented requirements this should be, like, <...>_ptr<Basic_blob>.
2060 using Ptr = typename Blob_ptr_container::value_type;
2061
2062 assert(out_blobs_ptr);
2063
2064 share_after_split_equally(size, headless_pool, [&](Basic_blob&& blob_moved)
2065 {
2066 out_blobs_ptr->push_back(Ptr{new Basic_blob{std::move(blob_moved)}});
2067 }, logger_ptr);
2068}
2069
2070template<typename Allocator_t, bool SHARING>
2073{
2074 static_assert(SHARING,
2075 "blobs_sharing() would only make sense on `Basic_blob`s with SHARING=true. "
2076 "Even if we were to allow this to instantiate (compile) it would always return false.");
2077
2078 return ((!blob1.zero()) && (!blob2.zero())) // Can't co-own a buffer if doesn't own a buffer.
2079 && ((&blob1 == &blob2) // Same object => same buffer.
2080 // Only share() (as of this writing) can lead to the underlying buffer's start ptr being identical.
2081 || ((blob1.begin() - blob1.start())
2082 == (blob2.begin() - blob2.start())));
2083 // @todo Maybe throw in assert(blob1.capacity() == blob2.capacity()), if `true` is being returned.
2084}
2085
2086template<typename Allocator_t, bool SHARING>
2088{
2089 return zero() ? 0 : m_size; // Note that zero() may or may not be true if we return 0.
2090}
2091
2092template<typename Allocator_t, bool SHARING>
2094{
2095 return zero() ? 0 : m_start; // Note that zero() may or may not be true if we return 0.
2096}
2097
2098template<typename Allocator_t, bool SHARING>
2100{
2101 return size() == 0; // Note that zero() may or may not be true if we return true.
2102}
2103
2104template<typename Allocator_t, bool SHARING>
2106{
2107 return zero() ? 0 : m_capacity; // Note that zero() <=> we return non-zero. (m_capacity >= 1 if !zero().)
2108}
2109
2110template<typename Allocator_t, bool SHARING>
2112{
2113 return !buf_ptr();
2114}
2115
2116template<typename Allocator_t, bool SHARING>
2118{
2119 reserve_impl(new_capacity, false, logger_ptr);
2120}
2121
2122template<typename Allocator_t, bool SHARING>
2124{
2125 reserve_impl(new_capacity, true, logger_ptr);
2126}
2127
2128template<typename Allocator_t, bool SHARING>
2129void Basic_blob<Allocator_t, SHARING>::reserve_impl(size_type new_capacity, bool clear_on_alloc, log::Logger* logger_ptr)
2130{
2131 using boost::make_shared_noinit;
2132 using boost::make_shared;
2133 using boost::shared_ptr;
2134 using std::numeric_limits;
2135 using std::memset;
2136
2137 /* As advertised do not allow enlarging existing buffer. They can call make_zero() though (but must do so consciously
2138 * hence considering the performance impact). */
2139 assert((zero() || ((new_capacity <= m_capacity) && (m_capacity > 0)))
2140 && "Basic_blob intentionally disallows reserving N>M>0, where M is current capacity. make_zero() first.");
2141
2142 /* OK, but what if new_capacity < m_capacity? Then post-condition (see below) is already satisfied, and it's fastest
2143 * to do nothing. If user believes lower memory use is higher-priority, they can explicitly call make_zero() first
2144 * but must make conscious decision to do so. */
2145
2146 if (zero() && (new_capacity != 0))
2147 {
2148 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
2149 {
2150 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
2151 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] "
2152 "allocating internal buffer sized [" << new_capacity << "]; "
2153 "will zero-fill? = [" << clear_on_alloc << "].");
2154 }
2155
2156 if (new_capacity <= size_type(numeric_limits<difference_type>::max())) // (See explanation near bottom of method.)
2157 {
2158 /* Time to (1) allocate the buffer; (2) save the pointer; (3) ensure it is deallocated at the right time
2159 * and with the right steps. Due to Allocator support this is a bit more complex than usual. Please
2160 * (1) see class doc header "Custom allocator" section; and (2) read Buf_ptr alias doc header for key background;
2161 * then come back here. */
2162
2163 if constexpr(S_IS_VANILLA_ALLOC)
2164 {
2165 /* In this case they specified std::allocator, so we are to just allocate/deallocate in regular heap using
2166 * new[]/delete[]. Hence we don't need to even use actual std::allocator; we know by definition it would
2167 * use new[]/delete[]. So simply use typical ..._ptr initialization. Caveats are unrelated to allocators:
2168 * - For some extra TRACE-logging -- if enabled! -- use an otherwise-vanilla logging deleter.
2169 * - Unnecessary in case of unique_ptr: dealloc always occurs in make_zero() or dtor and can be logged
2170 * there.
2171 * - If doing so (note it implies we've given up on performance) we cannot, and do not, use
2172 * make_shared*(); the use of custom deleter requires the .reset() form of init. */
2173
2174 /* If TRACE currently disabled, then skip the custom deleter that logs about dealloc. (TRACE may be enabled
2175 * by that point; but, hey, that is life.) This is for perf. */
2176 if constexpr(S_SHARING)
2177 {
2178 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
2179 {
2180 /* This ensures delete[] call when buf_ptr() ref-count reaches 0.
2181 * As advertised, for performance, the memory is NOT initialized unless so instructed. */
2182 buf_ptr().reset(clear_on_alloc ? (new value_type[new_capacity]()) : (new value_type[new_capacity]),
2183 // Careful! *this might be gone if some other share()ing obj is the one that 0s ref-count.
2184 [logger_ptr, original_blob = this, new_capacity]
2185 (value_type* buf_ptr_to_delete)
2186 {
2187 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
2188 FLOW_LOG_TRACE("Deallocating internal buffer sized [" << new_capacity << "] originally allocated by "
2189 "Blob [" << original_blob << "]; note that Blob may now be gone and furthermore another "
2190 "Blob might live at that address now. A message immediately preceding this one should "
2191 "indicate the last Blob to give up ownership of the internal buffer.");
2192 // Finally just do what the default one would've done, as we've done our custom thing (logging).
2193 delete [] buf_ptr_to_delete;
2194 });
2195 }
2196 else // if (!should_log()): No logging deleter; just delete[] it.
2197 {
2198 /* This executes `new value_type[new_capacity]` and ensures delete[] when buf_ptr() ref-count reaches 0.
2199 * As advertised, for performance, the memory is NOT initialized. */
2200 buf_ptr() = clear_on_alloc ? make_shared<value_type[]>(new_capacity)
2201 : make_shared_noinit<value_type[]>(new_capacity);
2202 }
2203 } // if constexpr(S_SHARING)
2204 else // if constexpr(!S_SHARING)
2205 {
2206 buf_ptr() = clear_on_alloc ? boost::movelib::make_unique<value_type[]>(new_capacity)
2207 : boost::movelib::make_unique_definit<value_type[]>(new_capacity);
2208 // Again -- the logging in make_zero() (and Blob_with_log_context dtor) is sufficient.
2209 }
2210 } // if constexpr(S_IS_VANILLA_ALLOC)
2211 else // if constexpr(!S_IS_VANILLA_ALLOC)
2212 {
2213 /* Fancy (well, potentially) allocator time. Again, if you've read the Buf_ptr and Deleter_raw doc headers,
2214 * you'll know what's going on. */
2215
2216 // Raw-allocate via Allocator! No value-init occurs... but see below.
2217 const auto ptr = alloc_raw().allocate(new_capacity);
2218
2219 if constexpr(S_SHARING)
2220 {
2221 buf_ptr().reset(ptr,
2222
2223 /* Let them allocate aux data (ref count block) via Allocator::allocate()
2224 * (and dealloc it -- ref count block -- via Allocator::deallocate())!
2225 * Have them store internal ptr bits as `Allocator::pointer`s, not
2226 * necessarily raw `value_type*`s! */
2227 alloc_raw(),
2228
2229 /* When the time comes to dealloc, invoke this guy like: D(<the ptr>)! It'll
2230 * perform alloc_raw().deallocate(<what .allocate() returned>, n).
2231 * Since only we happen to know the size of how much we actually allocated, we pass that info
2232 * into the Deleter_raw as well, as it needs to know the `n` to pass to
2233 * alloc_raw().deallocate(p, n). */
2234 Deleter_raw{alloc_raw(), new_capacity});
2235 /* Note: Unlike the S_IS_VANILLA_ALLOC=true case above, here we omit any attempt to log at the time
2236 * of dealloc, even if the verbosity is currently set high enough. It is not practical to achieve:
2237 * Recall that the assumptions we take for granted when dealing with std::allocator/regular heap
2238 * may no longer apply when dealing with an arbitrary allocator/potentially SHM-heap. To be able
2239 * to log at dealloc time, the Deleter_raw we create would need to store a Logger*. Sure, we
2240 * could pass-in logger_ptr and Deleter_raw could store it; but now recall that we do not
2241 * store a Logger* in `*this` and why: because (see class doc header) doing so does not play well
2242 * in some custom-allocator situations, particularly when operating in SHM-heap. That is why
2243 * we take an optional Logger* as an arg to every possibly-logging API (we can't guarantee, if
2244 * S_IS_VANILLA_ALLOC=false, that a Logger* can meaningfully be stored in likely-Allocator-stored *this).
2245 * For that same reason we cannot pass it to the Deleter_raw functor; buf_ptr() (whose bits are in
2246 * *this) will save a copy of that Deleter_raw and hence *this will end up storing the Logger* which
2247 * (as noted) may be nonsensical. (With S_IS_VANILLA_ALLOC=true, though, it's safe to store it; and
2248 * since deleter would only fire at dealloc time, it doesn't present a new perf problem -- since TRACE
2249 * log level alrady concedes bad perf -- which is the 2nd reason (see class doc header) for why
2250 * we don't generally record Logger* but rather take it as an arg to each logging API.)
2251 *
2252 * Anyway, long story short, we don't log on dealloc in this case, b/c we can't, and the worst that'll
2253 * happen as a result of that decision is: deallocs won't be trace-logged when a custom allocator
2254 * is enabled at compile-time. That price is okay to pay. */
2255 } // if constexpr(S_SHARING)
2256 else // if constexpr(!S_SHARING)
2257 {
2258 /* Conceptually it's quite similar to the S_SHARING case where we do shared_ptr::reset() above.
2259 * However there is an API difference that is subtle yet real (albeit only for stateful Allocator):
2260 * Current alloc_raw() was used to allocate *(buf_ptr()), so it must be used also to dealloc it.
2261 * unique_ptr::reset() does *not* take a new Deleter_raw; hence if we used it (alone) here it would retain
2262 * the alloc_raw() from ction (or possibly last assignment) time -- and if that does not equal current
2263 * m_alloc => trouble in make_zero() or dtor.
2264 *
2265 * Anyway, to beat that, we can manually overwrite get_deleter() (<-- non-const ref).
2266 * This will require a Deleter_raw move-assignment to exist (and it does, carefully and explicitly, as
2267 * of this writing; at least for it to be used here).
2268 * (Also: Caution! Recall buf_ptr() is null currently. If it were not
2269 * we would need to explicitly nullify it before the get_deleter() assignment.) */
2270 buf_ptr().get_deleter() = Deleter_raw{alloc_raw(), new_capacity};
2271 buf_ptr().reset(ptr);
2272 } // else if constexpr(!S_SHARING)
2273
2274 if (clear_on_alloc)
2275 {
2276 memset(ptr.get(), 0, new_capacity);
2277 /* Perf discussion: That is obviously correct functionally; but can it be done faster? Why do we ask?
2278 * Answer: See the S_IS_VANILLA_ALLOC case. Notice we use operations, when clear_on_alloc, that
2279 * will allocate-and-clear "at the same time." (More specifically, really, the make_shared()s and
2280 * the make_unique()s (as opposed to make_shared_noinit() or make_unique_definit()) promise to perform
2281 * `new T[]()` (as opposed to `new T[]`) which does in fact clear-while-allocating. Is that faster though?
2282 * Actually yes; empirically we've seen it be ~20% faster for a 64Ki buffer when comparing
2283 * `new T[N]()` versus `p = new T[N]; memset(p, 0, N)`; and theoretically perhaps `new T[]()` ends up
2284 * as `calloc()`, which in glibc might be clever -- making use of mmap()-ed areas being pre-zeroed,
2285 * knowing when a page is being dirtied by the calloc(), and so forth.) Here, though, we are not doing
2286 * any such thing; we simply A.allocate(N) via allocator (not std::allocator; possibly SHM-aware) --
2287 * then zero it. The user could even do it themselves, in this case making clear_on_alloc syntactic
2288 * sugar at best. So could we do better, like we did in the S_IS_VANILLA_ALLOC=true case above?
2289 * Answer: Well... I (ygoldfel) think... no, not per se. Not here at least. We do have to use the
2290 * Allocator, and nothing in the C++1x or C++17 Allocator concept docs suggests it is possible to
2291 * ask it to allocate-and-clear. We only did so for S_IS_VANILLA_ALLOC=true, because we know
2292 * std::allocator by definition does heap new/delete; so we can call such things ourselves and not actually
2293 * mention the Allocator; it is used more as a binary determinant of S_IS_VANILLA_ALLOC=true.
2294 * So by contract, since there's no way to alloc-and-zero at the same time, if we are told to
2295 * clear_on_alloc, then we have to memset(); no choice.
2296 *
2297 * @todo However the code (as of this writing at least in Flow-IPC's SHM-related code including
2298 * ipc::transport::struc::shm::Capnp_message_builder) that uses Basic_blob and *can* possibly guarantee
2299 * that Allocator::allocate(N) will pre-zero the N bytes as-needed -- such code could
2300 * (1) specify clear_on_alloc=false and (2) explicitly guarantee .allocate() will alloc-and-zero.
2301 * As of this writing that is in Flow-IPC (not Flow), a sister/dependent component that shares Flow's DNA;
2302 * and in particular in that case we've got:
2303 * - SHM-classic: Ultimately it leverages boost::interprocess::managed_shared_memory; look into it
2304 * whether there's some kind of sped-up alloc-and-zero hook available.
2305 * - SHM-jemalloc: That one has a home-grown jemalloc extension; it might be possible to use some kind of
2306 * knob(s) to ensure an alloc-and-zero is performed. MALLOCX_ZERO flag to mallocx() will do it, and
2307 * docs suggest care is taken to do this performantly; perhaps this mode can be set/unset through some
2308 * kind of thread-local config system -- as long as it is not slow -- not sure. As of this writing
2309 * there's already a mandatory activator-context object, so maybe it could be merely extended with
2310 * this knob. Not sure... it's doable though.
2311 * - Either way: The user that does must be careful; zeroing in *all* allocs would probably be bad, as
2312 * many situations do not require it; it should only be done when actually desired.
2313 * To be clear: all that is out of Basic_blob's purview; so really this to-do should be elsewhere arguably;
2314 * but it's a closely related project, so here is better than nowhere. Plus it provides some non-obvious
2315 * context. */
2316 } // if (clear_on_alloc)
2317 } // else if constexpr(!S_IS_VANILLA_ALLOC)
2318 } // if (new_capacity <= numeric_limits<difference_type>::max()) // (See explanation just below.)
2319 else
2320 {
2321 assert(false && "Enormous or corrupt new_capacity?!");
2322 }
2323 /* ^-- Explanation of the strange if/else:
2324 * In some gcc versions in some build configs, particularly with aggressive auto-inlining optimization,
2325 * a warning like this can be triggered (observed, as of this writing, only in the movelib::make_unique_definit()
2326 * branch above, but to be safe we're covering all the branches with our if/else work-around):
2327 * argument 1 value '18446744073709551608' exceeds maximum object size
2328 * 9223372036854775807 [-Werror=alloc-size-larger-than=]
2329 * This occurs due to (among other things) inlining from above our frame down into the boost::movelib call
2330 * we make (and potentially the other allocating calls in the various branches above);
2331 * plus allegedly the C++ front-end supplying the huge value during the diagnostics pass.
2332 * No such huge value (which is 0xFFFF'FFFF'FFFF'FFF8) is actually passed-in at run-time nor mentioned anywhere
2333 * in our code, here or in the unit-test(s) triggering the auto-inlining triggering the warning. So:
2334 *
2335 * The warning is wholly inaccurate. This situation is known in the gcc issue database; for example
2336 * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85783 and related (linked) tickets.
2337 * The question was how to work around it; I admit that the discussion in that ticket (and friends) at times
2338 * gets into topics so obscure and gcc-internal as to be indecipherable to me (ygoldfel).
2339 * Since I don't seem to be doing anything wrong above (though: @todo *Maybe* it has something to do with
2340 * lacking `nothrow`? Would need investigation, nothrow could be good anyway...), the top work-arounds would be
2341 * perhaps: 1, pragma-away the alloc-size-larger-than warning; 2, use a compiler-placating
2342 * explicit `if (new_capacity < ...limit...)` branch. (2) was suggested in the above ticket by a
2343 * gcc person. Not wanting to give even a tiny bit of perf I attempted the pragma way (1); but at least gcc-13
2344 * has some bug which makes the pragma get ignored. So I reverted to (2) by default.
2345 * @todo Revisit this. Should skip workaround unless gcc; + possibly solve it some more elegant way; look into the
2346 * nothrow thing the ticket discussion briefly mentions (but might be irrelevant). */
2347
2348 m_capacity = new_capacity;
2349 m_size = 0; // Went from zero() to !zero(); so m_size went from meaningless to meaningful and must be set.
2350 m_start = 0; // Ditto for m_start.
2351
2352 assert(!zero());
2353 // This is the only path (other than swap()) that assigns to m_capacity; note m_capacity >= 1.
2354 }
2355 /* else { !zero(): Since new_capacity <= m_capacity, m_capacity is already large enough; no change needed.
2356 * zero() && (new_capacity == 0): Since 0-capacity wanted, we can continue being zero(), as that's enough. } */
2357
2358 assert(capacity() >= new_capacity); // Promised post-condition.
2359} // Basic_blob::reserve()
2360
2361template<typename Allocator_t, bool SHARING>
2363 log::Logger* logger_ptr)
2364{
2365 resize_impl(new_size, false, new_start_or_unchanged, logger_ptr);
2366}
2367
2368template<typename Allocator_t, bool SHARING>
2370 size_type new_start_or_unchanged, log::Logger* logger_ptr)
2371{
2372 resize_impl(new_size, true, new_start_or_unchanged, logger_ptr);
2373}
2374
2375template<typename Allocator_t, bool SHARING>
2377 size_type new_start_or_unchanged, log::Logger* logger_ptr)
2378{
2379 auto& new_start = new_start_or_unchanged;
2380 if (new_start == S_UNCHANGED)
2381 {
2382 new_start = start();
2383 }
2384
2385 const size_type min_capacity = new_start + new_size;
2386
2387 /* Ensure there is enough space for new_size starting at new_start. Note, in particular, this disallows
2388 * enlarging non-zero() buffer.
2389 * (If they want, they can explicitly call make_zero() first. But they must do so consciously, so that they're
2390 * forced to consider the performance impact of such an action.) Also note that zero() continues to be true
2391 * if was true. */
2392 reserve_impl(min_capacity, clear_on_alloc, logger_ptr);
2393 assert(capacity() >= min_capacity);
2394
2395 if (!zero())
2396 {
2397 m_size = new_size;
2398 m_start = new_start;
2399 }
2400 // else { zero(): m_size, m_start are meaningless; size() and start() == 0, as desired. }
2401
2402 assert(size() == new_size);
2403 assert(start() == new_start);
2404} // Basic_blob::resize()
2405
2406template<typename Allocator_t, bool SHARING>
2408{
2409 resize(((start() + size()) > prefix_size)
2410 ? (start() + size() - prefix_size)
2411 : 0,
2412 prefix_size); // It won't log, as it cannot allocate, so no need to pass-through a Logger*.
2413 // Sanity check: `prefix_size == 0` translates to: resize(start() + size(), 0), as advertised.
2414}
2415
2416template<typename Allocator_t, bool SHARING>
2418{
2419 assert((prefix_size_inc >= 0) || (start() >= size_type(-prefix_size_inc)));
2420 start_past_prefix(start() + prefix_size_inc);
2421}
2422
2423template<typename Allocator_t, bool SHARING>
2425{
2426 // Note: start() remains unchanged (as advertised). resize(0, 0) can be used if that is unacceptable.
2427 resize(0); // It won't log, as it cannot allocate, so no need to pass-through a Logger*.
2428 // Note corner case: zero() remains true if was true (and false if was false).
2429}
2430
2431template<typename Allocator_t, bool SHARING>
2433{
2434 /* Could also write more elegantly: `swap(Basic_blob{});`, but following is a bit optimized (while equivalent);
2435 * logs better. */
2436 if (!zero())
2437 {
2438 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
2439 {
2440 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
2441 if constexpr(SHARING)
2442 {
2443 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] giving up ownership of internal buffer sized "
2444 "[" << capacity() << "]; deallocation will immediately follow if no sharing "
2445 "`Blob`s remain; else ref-count merely decremented.");
2446 }
2447 else
2448 {
2449 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] deallocating internal buffer sized "
2450 "[" << capacity() << "].");
2451 }
2452 }
2453
2454 buf_ptr().reset();
2455 } // if (!zero())
2456} // Basic_blob::make_zero()
2457
2458template<typename Allocator_t, bool SHARING>
2460 Basic_blob<Allocator_t, SHARING>::assign_copy(const boost::asio::const_buffer& src, log::Logger* logger_ptr)
2461{
2462 const size_type n = src.size();
2463
2464 /* Either just set m_start = 0 and decrease/keep-constant (m_start + m_size) = n; or allocate exactly n-sized buffer
2465 * and set m_start = 0, m_size = n.
2466 * As elsewhere, the latter case requires that zero() be true currently (but they can force that with make_zero()). */
2467 resize(n, 0, logger_ptr);
2468
2469 // Performance: Basically equals: memcpy(buf_ptr(), src.start, src.size).
2470 emplace_copy(const_begin(), src, logger_ptr);
2471
2472 // Corner case: n == 0. Above is equivalent to: if (!zero()) { m_size = m_start = 0; }. That behavior is advertised.
2473
2474 return n;
2475}
2476
2477template<typename Allocator_t, bool SHARING>
2479 Basic_blob<Allocator_t, SHARING>::emplace_copy(Const_iterator dest, const boost::asio::const_buffer& src,
2480 log::Logger* logger_ptr)
2481{
2482 using std::memcpy;
2483
2484 // Performance: assert()s eliminated and values inlined, below boils down to: memcpy(dest, src.start, src.size);
2485
2486 assert(valid_iterator(dest));
2487
2488 const Iterator dest_it = iterator_sans_const(dest);
2489 const size_type n = src.size(); // Note the entire source buffer is copied over.
2490
2491 if (n != 0)
2492 {
2493 const auto src_data = static_cast<Const_iterator>(src.data());
2494
2495 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
2496 {
2497 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
2498 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] copying "
2499 "memory area [" << static_cast<const void*>(src_data) << "] sized "
2500 "[" << n << "] to internal buffer at offset [" << (dest - const_begin()) << "].");
2501 }
2502
2503 assert(derefable_iterator(dest_it)); // Input check.
2504 assert(difference_type(n) <= (const_end() - dest)); // Input check. (As advertised, we don't "correct" `n`.)
2505
2506 // Ensure no overlap by user.
2507 assert(((dest_it + n) <= src_data) || ((src_data + n) <= dest_it));
2508
2509 /* Some compilers in some build configs issue stringop-overflow warning here, when optimizer heavily auto-inlines:
2510 * error: 'memcpy' specified bound between 9223372036854775808 and 18446744073709551615
2511 * exceeds maximum object size 9223372036854775807 [-Werror=stringop-overflow=]
2512 * This occurs due to (among other things) inlining from above our frame down into the std::memcpy() call
2513 * we make; plus allegedly the C++ front-end supplying the huge values during the diagnostics pass.
2514 * No such huge values (which are 0x8000'0000'0000'000F, 0xFFFF'FFFF'FFFF'FFFF, 0x7FFF'FFFF'FFFF'FFFF, respectively)
2515 * are actually passed-in at run-time nor mentioned anywhere
2516 * in our code, here or in the unit-test(s) triggering the auto-inlining triggering the warning. So:
2517 *
2518 * The warning is wholly inaccurate in a way reminiscent of the situation in reserve() with a somewhat
2519 * similar comment. In this case, however, a pragma does properly work, so we use that approach instead of
2520 * a run-time check/assert() which would give away a bit of perf. */
2521#pragma GCC diagnostic push
2522#pragma GCC diagnostic ignored "-Wpragmas" // For older versions, where the following does not exist/cannot be disabled.
2523#pragma GCC diagnostic ignored "-Wunknown-warning-option" // (Similarly for clang.)
2524#pragma GCC diagnostic ignored "-Wstringop-overflow"
2525#pragma GCC diagnostic ignored "-Wrestrict" // Another similar bogus one pops up after pragma-ing away preceding one.
2526
2527 /* Likely linear-time in `n` but hopefully optimized. Could use a C++ construct, but I've seen that be slower
2528 * than a direct memcpy() call in practice, at least in a Linux gcc. Could use boost.asio buffer_copy(), which
2529 * as of this writing does do memcpy(), but the following is an absolute guarantee of best performance, so better
2530 * safe than sorry (hence this whole Basic_blob class's existence, at least in part). */
2531 memcpy(dest_it, src_data, n);
2532
2533#pragma GCC diagnostic pop
2534 }
2535
2536 return dest_it + n;
2537} // Basic_blob::emplace_copy()
2538
2539template<typename Allocator_t, bool SHARING>
2541 Basic_blob<Allocator_t, SHARING>::sub_copy(Const_iterator src, const boost::asio::mutable_buffer& dest,
2542 log::Logger* logger_ptr) const
2543{
2544 // Code similar to emplace_copy(). Therefore keeping comments light.
2545
2546 using std::memcpy;
2547
2548 assert(valid_iterator(src));
2549
2550 const size_type n = dest.size(); // Note the entire destination buffer is filled.
2551 if (n != 0)
2552 {
2553 const auto dest_data = static_cast<Iterator>(dest.data());
2554
2555 if (logger_ptr && logger_ptr->should_log(log::Sev::S_TRACE, S_LOG_COMPONENT))
2556 {
2557 FLOW_LOG_SET_CONTEXT(logger_ptr, S_LOG_COMPONENT);
2558 FLOW_LOG_TRACE_WITHOUT_CHECKING("Blob [" << this << "] copying to "
2559 "memory area [" << static_cast<const void*>(dest_data) << "] sized "
2560 "[" << n << "] from internal buffer offset [" << (src - const_begin()) << "].");
2561 }
2562
2563 assert(derefable_iterator(src));
2564 assert(difference_type(n) <= (const_end() - src)); // Can't copy from beyond end of *this blob.
2565
2566 assert(((src + n) <= dest_data) || ((dest_data + n) <= src));
2567
2568 // See explanation for the pragma in emplace_copy(). While warning not yet observed here, preempting it.
2569#pragma GCC diagnostic push
2570#pragma GCC diagnostic ignored "-Wpragmas" // For older versions, where the following does not exist/cannot be disabled.
2571#pragma GCC diagnostic ignored "-Wunknown-warning-option" // (Similarly for clang.)
2572#pragma GCC diagnostic ignored "-Wstringop-overflow"
2573#pragma GCC diagnostic ignored "-Wrestrict"
2574 memcpy(dest_data, src, n);
2575#pragma GCC diagnostic pop
2576 }
2577
2578 return src + n;
2579}
2580
2581template<typename Allocator_t, bool SHARING>
2584{
2585 using std::memmove;
2586
2587 assert(derefable_iterator(first)); // Input check.
2588 assert(valid_iterator(past_last)); // Input check.
2589
2590 const Iterator dest = iterator_sans_const(first);
2591
2592 if (past_last > first) // (Note: `past_last <= first` allowed, not illegal.)
2593 {
2594 const auto n_moved = size_type(const_end() - past_last);
2595
2596 if (n_moved != 0)
2597 {
2598 // See explanation for the pragma in emplace_copy(). While warning not yet observed here, preempting it.
2599#pragma GCC diagnostic push
2600#pragma GCC diagnostic ignored "-Wpragmas" // For older versions, where the following does not exist/cannot be disabled.
2601#pragma GCC diagnostic ignored "-Wunknown-warning-option" // (Similarly for clang.)
2602#pragma GCC diagnostic ignored "-Wstringop-overflow"
2603#pragma GCC diagnostic ignored "-Wrestrict"
2604 memmove(dest, iterator_sans_const(past_last), n_moved); // Cannot use memcpy() due to possible overlap.
2605#pragma GCC diagnostic pop
2606 }
2607 // else { Everything past end() is to be erased: it's sufficient to just update m_size: }
2608
2609 m_size -= (past_last - first);
2610 // m_capacity does not change, as we advertised minimal operations possible to achieve result.
2611 } // if (past_last > first)
2612 // else if (past_last <= first) { Nothing to do. }
2613
2614 return dest;
2615} // Basic_blob::erase()
2616
2617template<typename Allocator_t, bool SHARING>
2620{
2621 assert(!empty());
2622 return *const_begin();
2623}
2624
2625template<typename Allocator_t, bool SHARING>
2628{
2629 assert(!empty());
2630 return const_end()[-1];
2631}
2632
2633template<typename Allocator_t, bool SHARING>
2636{
2637 assert(!empty());
2638 return *begin();
2639}
2640
2641template<typename Allocator_t, bool SHARING>
2644{
2645 assert(!empty());
2646 return end()[-1];
2647}
2648
2649template<typename Allocator_t, bool SHARING>
2652{
2653 return const_front();
2654}
2655
2656template<typename Allocator_t, bool SHARING>
2659{
2660 return const_back();
2661}
2662
2663template<typename Allocator_t, bool SHARING>
2666{
2667 return const_cast<Basic_blob*>(this)->begin();
2668}
2669
2670template<typename Allocator_t, bool SHARING>
2673{
2674 if (zero())
2675 {
2676 return nullptr;
2677 }
2678 // else
2679
2680 /* buf_ptr().get() is value_type* when Buf_ptr = regular shared_ptr; but possibly Some_fancy_ptr<value_type>
2681 * when Buf_ptr = boost::interprocess::shared_ptr<value_type, Allocator>, namely when
2682 * Allocator::pointer = Some_fancy_ptr<value_type> and not simply value_type* again. We need value_type*.
2683 * Fancy-pointer is not really an officially-defined concept (offset_ptr<> is an example of one).
2684 * Anyway the following works for both cases, but there are a bunch of different things we could write.
2685 * Since it's just this one location where we need to do this, I do not care too much, and the following
2686 * cheesy thing -- &(*p) -- is OK.
2687 *
2688 * @todo In C++20 can replace this with std::to_address(). Or can implement our own (copy cppreference.com impl). */
2689
2690 const auto raw_or_fancy_buf_ptr = buf_ptr().get();
2691 return &(*raw_or_fancy_buf_ptr) + m_start;
2692}
2693
2694template<typename Allocator_t, bool SHARING>
2697{
2698 return zero() ? const_begin() : (const_begin() + size());
2699}
2700
2701template<typename Allocator_t, bool SHARING>
2704{
2705 return zero() ? begin() : (begin() + size());
2706}
2707
2708template<typename Allocator_t, bool SHARING>
2711{
2712 return const_begin();
2713}
2714
2715template<typename Allocator_t, bool SHARING>
2718{
2719 return const_begin();
2720}
2721
2722template<typename Allocator_t, bool SHARING>
2725{
2726 return const_end();
2727}
2728
2729template<typename Allocator_t, bool SHARING>
2732{
2733 return const_end();
2734}
2735
2736template<typename Allocator_t, bool SHARING>
2739{
2740 return const_begin();
2741}
2742
2743template<typename Allocator_t, bool SHARING>
2746{
2747 return begin();
2748}
2749
2750template<typename Allocator_t, bool SHARING>
2752{
2753 return empty() ? (it == const_end())
2754 : in_closed_range(const_begin(), it, const_end());
2755}
2756
2757template<typename Allocator_t, bool SHARING>
2759{
2760 return empty() ? false
2761 : in_closed_open_range(const_begin(), it, const_end());
2762}
2763
2764template<typename Allocator_t, bool SHARING>
2767{
2768 return const_cast<value_type*>(it); // Can be done without const_cast<> but might as well save some cycles.
2769}
2770
2771template<typename Allocator_t, bool SHARING>
2772boost::asio::const_buffer Basic_blob<Allocator_t, SHARING>::const_buffer() const
2773{
2774 return boost::asio::const_buffer{const_data(), size()};
2775}
2776
2777template<typename Allocator_t, bool SHARING>
2779{
2780 return boost::asio::mutable_buffer{data(), size()};
2781}
2782
2783template<typename Allocator_t, bool SHARING>
2786{
2787 return alloc_raw();
2788}
2789
2790template<typename Allocator_t, bool SHARING>
2792{
2793 return m_alloc_and_buf_ptr.second();
2794}
2795
2796template<typename Allocator_t, bool SHARING>
2799{
2800 return const_cast<Basic_blob*>(this)->buf_ptr();
2801}
2802
2803template<typename Allocator_t, bool SHARING>
2805{
2806 return m_alloc_and_buf_ptr.first();
2807}
2808
2809template<typename Allocator_t, bool SHARING>
2812{
2813 return const_cast<Basic_blob*>(this)->alloc_raw();
2814}
2815
2816
2817template<typename Allocator_t, bool SHARING>
2819 m_buf_sz(0)
2820{
2821 /* It can be left `= default;`, but some gcc versions then complain m_buf_sz may be used uninitialized (not true but
2822 * such is life). */
2823}
2824
2825template<typename Allocator_t, bool SHARING>
2827 /* Copy allocator; a stateless allocator should have size 0 (no-op for the processor in that case... except
2828 * the optional<> registering it has-a-value). */
2829 m_alloc_raw(std::in_place, alloc_raw_src),
2830 m_buf_sz(buf_sz) // Smart-ptr stores a T*, where T is a trivial-deleter PoD, but we delete an array of Ts: this many.
2831{
2832 // OK.
2833}
2834
2835template<typename Allocator_t, bool SHARING>
2837{
2838 /* We advertised our action is as-if we default-ct, then move-assign. While we skipped delegating to default-ctor,
2839 * the only difference is that would've initialized m_buf_sz; but the following will just overwrite it anyway. So
2840 * we can in fact move-assign now, and that's it. */
2841 operator=(std::move(moved_src));
2842}
2843
2844/* Auto-generated copy-ct should be fine; the only conceivable source of trouble might be Allocator copy-ction,
2845 * but that must exist for all allocators. */
2846template<typename Allocator_t, bool SHARING>
2848
2849template<typename Allocator_t, bool SHARING>
2852{
2853 using std::swap;
2854
2855 if (this != &moved_src) // @todo Maybe assert() on this, since our uses are so locked-down?
2856 {
2857 m_buf_sz = 0;
2858 swap(m_buf_sz, moved_src.m_buf_sz);
2859
2860 /* That's that for m_buf_sz; that leaves m_alloc_raw. That is trickier than one might think; a swap
2861 * or explicitly copy- or move-assigning it will work with many allocators, but some are not assignable at all
2862 * (for example boost::interprocess::allocator which is stateful). (There are good reasons for that having to
2863 * do with propagate_on_container_*_assignment, but never mind; our task here is simpler than those worries.)
2864 * Bottom line is, every allocator is copy-constructible, and we store m_alloc_raw as an optional<>, so
2865 * we can simulate an assignment via destroy (if needed) + copy-construction, namely using optional::emplace().
2866 *
2867 * Plus, arguably an optimization: it is very common they're the same allocator by-value (e.g., stateless
2868 * allocators of the same class are always mutually equal, period); in which case can no-op. */
2869 if (!moved_src.m_alloc_raw)
2870 {
2871 // Another corner case. @todo Maybe assert() on this not being the case, since our uses are so locked-down?
2872 m_alloc_raw.reset();
2873 // m_alloc_raw has been as-if copied over; and moved_src's guy is already as-if default-cted, as promised.
2874 }
2875 else
2876 {
2877 const auto& src_alloc_raw = *moved_src.m_alloc_raw;
2878 if ((!m_alloc_raw) || (*m_alloc_raw != src_alloc_raw))
2879 {
2880 m_alloc_raw.emplace(src_alloc_raw); // Destroy if needed; then copy-construct.
2881 }
2882 // else { m_alloc_raw is already as-if copied from moved_src.m_alloc_raw: the aforementioned optimization. }
2883
2884 // m_alloc_raw has been copied over; as promised reset moved_src's guy to as-if-default-cted.
2885 moved_src.m_alloc_raw.reset();
2886 }
2887 } // if (this != &moved_src)
2888
2889 return *this;
2890} // Basic_blob::Deleter_raw::operator=(&&)
2891
2892template<typename Allocator_t, bool SHARING>
2895{
2896 /* Ideally we'd just use `= default;`, but that might not compile, when Allocator has no copy-assignment
2897 * (as noted elsewhere this is entirely possible). So basically perform a simpler version of the move-assignment
2898 * impl. Keeping comments light; please see move-assignment impl. */
2899
2900 if (this != &src)
2901 {
2902 m_buf_sz = src.m_buf_sz;
2903
2904 if (!src.m_alloc_raw)
2905 {
2906 m_alloc_raw.reset();
2907 }
2908 else
2909 {
2910 const auto& src_alloc_raw = *src.m_alloc_raw;
2911 if ((!m_alloc_raw) || (*m_alloc_raw != src_alloc_raw))
2912 {
2913 m_alloc_raw.emplace(src_alloc_raw); // Having to do this for some `Allocator`s is why we can't `= default;`.
2914 }
2915 }
2916 } // if (this != &src)
2917
2918 return *this;
2919} // Basic_blob::Deleter_raw::operator=(const&)
2920
2921template<typename Allocator_t, bool SHARING>
2923{
2924 // No need to invoke dtor: Allocator::value_type is Basic_blob::value_type, a boring int type with no real dtor.
2925
2926 // Free the raw buffer at location to_delete; which we know is m_buf_sz `value_type`s long.
2927 m_alloc_raw->deallocate(to_delete, m_buf_sz);
2928}
2929
2930} // namespace flow::util
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1392
virtual bool should_log(Sev sev, const Component &component) const =0
Given attributes of a hypothetical message that would be logged, return true if that message should b...
Internal deleter functor used if and only if S_IS_VANILLA_ALLOC is false and therefore only with Buf_...
std::optional< Allocator > m_alloc_raw
See ctor: the allocator that operator()() shall use to deallocate.
typename std::allocator_traits< Allocator >::pointer Pointer_raw
Short-hand for the allocator's pointer type, pointing to Basic_blob::value_type.
size_type m_buf_sz
See ctor and operator()(): the size of the buffer to deallocate.
Pointer_raw pointer
For boost::interprocess::shared_ptr and unique_ptr compliance (hence the irregular capitalization).
void operator()(Pointer_raw to_delete)
Deallocates using Allocator::deallocate(), passing-in the supplied pointer (to value_type) to_delete ...
Deleter_raw(const Deleter_raw &src)
Copy-construction which may be required when we are used in boost::interprocess::shared_ptr which as ...
Deleter_raw & operator=(Deleter_raw &&moved_src)
Move-assignment which is required when we are used in unique_ptr.
Deleter_raw()
Default ctor: Deleter must never be invoked to delete anything in this state; suitable only for a nul...
A hand-optimized and API-tweaked replacement for vector<uint8_t>, i.e., buffer of bytes inside an all...
Definition: basic_blob.hpp:277
std::conditional_t< S_IS_VANILLA_ALLOC, std::conditional_t< S_SHARING, boost::shared_ptr< value_type[]>, boost::movelib::unique_ptr< value_type[]> >, std::conditional_t< S_SHARING, boost::interprocess::shared_ptr< value_type, Allocator, Deleter_raw >, boost::movelib::unique_ptr< value_type, Deleter_raw > > > Buf_ptr
The smart-pointer type used for buf_ptr(); a custom-allocator-and-SHM-friendly impl and parameterizat...
size_type start() const
Returns the offset between begin() and the start of the internally allocated buffer.
~Basic_blob()
Destructor that drops *this ownership of the allocated internal buffer if any, as by make_zero(); if ...
size_type m_capacity
See capacity(); but m_capacity is meaningless (and containing unknown value) if !buf_ptr() (i....
void start_past_prefix_inc(difference_type prefix_size_inc)
Like start_past_prefix() but shifts the current prefix position by the given incremental value (posit...
const value_type & const_back() const
Returns reference to immutable last element.
Const_iterator cend() const
Synonym of const_end().
Iterator pointer
For container compliance (hence the irregular capitalization): pointer to element.
Definition: basic_blob.hpp:302
value_type const * const_data() const
Equivalent to const_begin().
Const_iterator const_end() const
Returns pointer one past immutable last element; empty() is possible.
void resize(size_type size, size_type start_or_unchanged=S_UNCHANGED, log::Logger *logger_ptr=nullptr)
Guarantees post-condition size() == size and start() == start; no values in pre-call range [begin(),...
Basic_blob(const Allocator &alloc_raw_src={})
Constructs blob with zero() == true.
Basic_blob(Basic_blob &&moved_src, log::Logger *logger_ptr=nullptr) noexcept
Move constructor, constructing a blob exactly internally equal to pre-call moved_src,...
size_type m_start
See start(); but m_start is meaningless (and containing unknown value) if !buf_ptr() (i....
size_type size() const
Returns number of elements stored, namely end() - begin().
void start_past_prefix(size_type prefix_size)
Restructures blob to consist of an internally allocated buffer and a [begin(), end) range starting at...
bool empty() const
Returns size() == 0.
size_type assign_copy(const boost::asio::const_buffer &src, log::Logger *logger_ptr=nullptr)
Replaces logical contents with a copy of the given non-overlapping area anywhere in memory.
Allocator get_allocator() const
Returns a copy of the internally cached Allocator as set by a constructor or assign() or assignment-o...
void share_after_split_equally(size_type size, bool headless_pool, Emit_blob_func &&emit_blob_func, log::Logger *logger_ptr=nullptr)
Identical to successively performing share_after_split_left(size) until this->empty() == true; the re...
value_type const * Const_iterator
Type for iterator pointing into an immutable structure of this type.
Definition: basic_blob.hpp:294
Basic_blob & operator=(Basic_blob &&moved_src) noexcept
Move assignment operator (no logging): equivalent to assign(std::move(moved_src)).
std::ptrdiff_t difference_type
Type for difference of size_types.
Definition: basic_blob.hpp:288
Const_iterator cbegin() const
Synonym of const_begin().
Const_iterator sub_copy(Const_iterator src, const boost::asio::mutable_buffer &dest, log::Logger *logger_ptr=nullptr) const
The opposite of emplace_copy() in every way, copying a sub-blob onto a target memory area.
void swap(Basic_blob &other, log::Logger *logger_ptr=nullptr) noexcept
Swaps the contents of this structure and other, or no-op if this == &other.
Basic_blob share_after_split_right(size_type size, log::Logger *logger_ptr=nullptr)
Identical to share_after_split_left(), except this->end() shifts by size to the left (instead of this...
bool zero() const
Returns false if a buffer is allocated and owned; true otherwise.
size_type m_size
See size(); but m_size is meaningless (and containing unknown value) if !buf_ptr() (i....
std::size_t size_type
Type for index into blob or length of blob or sub-blob.
Definition: basic_blob.hpp:285
const Allocator & alloc_raw() const
Ref-to-immutable counterpart to the other overload.
Iterator emplace_copy(Const_iterator dest, const boost::asio::const_buffer &src, log::Logger *logger_ptr=nullptr)
Copies src buffer directly onto equally sized area within *this at location dest; *this must have suf...
Allocator & alloc_raw()
For convenience/expressiveness, the allocator object for *this.
std::allocator< uint8_t > Allocator
Short-hand for the allocator type specified at compile-time. Its element type is our value_type.
Definition: basic_blob.hpp:297
Basic_blob share_after_split_left(size_type size, log::Logger *logger_ptr=nullptr)
Applicable to !zero() blobs, this shifts this->begin() by size to the right without changing end(); a...
bool derefable_iterator(Const_iterator it) const
Returns true if and only if the given iterator points to an element within this blob's size() element...
const value_type & front() const
Equivalent to const_front().
Iterator end()
Returns pointer one past mutable last element; empty() is possible.
void make_zero(log::Logger *logger_ptr=nullptr)
Guarantees post-condition zero() == true by dropping *this ownership of the allocated internal buffer...
void reserve_impl(size_type capacity, bool clear_on_alloc, log::Logger *logger_ptr)
Implements reserve() overloads.
Const_iterator const_pointer
For container compliance (hence the irregular capitalization): pointer to const element.
Definition: basic_blob.hpp:304
static constexpr size_type S_UNCHANGED
Special value indicating an unchanged size_type value; such as in resize().
Definition: basic_blob.hpp:320
size_type capacity() const
Returns the number of elements in the internally allocated buffer, which is 1 or more; or 0 if no buf...
void clear()
Equivalent to resize(0, start()).
void swap_impl(Basic_blob &other, log::Logger *logger_ptr) noexcept
The body of swap(), except for the part that swaps (or decides not to swap) alloc_raw().
bool valid_iterator(Const_iterator it) const
Returns true if and only if: this->derefable_iterator(it) || (it == this->const_end()).
static constexpr Flow_log_component S_LOG_COMPONENT
Our flow::log::Component.
Const_iterator const_begin() const
Returns pointer to immutable first element; or end() if empty().
void reserve(size_type capacity, log::Logger *logger_ptr=nullptr)
Ensures that an internal buffer of at least capacity elements is allocated and owned; disallows growi...
boost::asio::const_buffer const_buffer() const
Convenience accessor returning an immutable boost.asio buffer "view" into the entirety of the blob.
void share_after_split_equally_emit_ptr_seq(size_type size, bool headless_pool, Blob_ptr_container *out_blobs, log::Logger *logger_ptr=nullptr)
share_after_split_equally() wrapper that places Ptr<Basic_blob>s into the given container via push_ba...
void resize_impl(size_type size, bool clear_on_alloc, size_type start_or_unchanged, log::Logger *logger_ptr)
Implements resize() overloads.
value_type * data()
Equivalent to begin().
const Buf_ptr & buf_ptr() const
Ref-to-immutable counterpart to the other overload.
Basic_blob & assign(Basic_blob &&moved_src, log::Logger *logger_ptr=nullptr) noexcept
Move assignment.
boost::compressed_pair< Allocator, Buf_ptr > m_alloc_and_buf_ptr
Combined – to enable empty base-class optimization (EBO) – storage for the two data items,...
const value_type & const_front() const
Returns reference to immutable first element.
void share_after_split_equally_impl(size_type size, bool headless_pool, Emit_blob_func &&emit_blob_func, log::Logger *logger_ptr, Share_after_split_left_func &&share_after_split_left_func)
Impl of share_after_split_equally() but capable of emitting not just *this type (Basic_blob<....
Basic_blob(size_type size, Clear_on_alloc coa_tag, log::Logger *logger_ptr=nullptr, const Allocator &alloc_raw_src={})
Identical to similar-sig ctor except, if size > 0, all size elements are performantly initialized to ...
Const_iterator const_iterator
For container compliance (hence the irregular capitalization): Const_iterator type.
Definition: basic_blob.hpp:312
void share_after_split_equally_emit_seq(size_type size, bool headless_pool, Blob_container *out_blobs, log::Logger *logger_ptr=nullptr)
share_after_split_equally() wrapper that places Basic_blobs into the given container via push_back().
static constexpr bool S_IS_VANILLA_ALLOC
true if Allocator underlying allocator template is simply std::allocator; false otherwise.
Definition: basic_blob.hpp:360
value_type & reference
For container compliance (hence the irregular capitalization): reference to element.
Definition: basic_blob.hpp:306
Basic_blob(size_type size, log::Logger *logger_ptr=nullptr, const Allocator &alloc_raw_src={})
Constructs blob with size() and capacity() equal to the given size, and start() == 0.
uint8_t value_type
Short-hand for values, which in this case are unsigned bytes.
Definition: basic_blob.hpp:282
value_type * Iterator
Type for iterator pointing into a mutable structure of this type.
Definition: basic_blob.hpp:291
Buf_ptr & buf_ptr()
For convenience/expressiveness, the pointer-to-main-buf for *this.
Iterator iterator
For container compliance (hence the irregular capitalization): Iterator type.
Definition: basic_blob.hpp:310
static constexpr bool S_SHARING
Value of template parameter SHARING (for generic programming).
Definition: basic_blob.hpp:317
Iterator begin()
Returns pointer to mutable first element; or end() if empty().
const value_type & back() const
Equivalent to const_back().
Basic_blob share(log::Logger *logger_ptr=nullptr) const
Applicable to !zero() blobs, this returns an identical Basic_blob that shares (co-owns) *this allocat...
const value_type & const_reference
For container compliance (hence the irregular capitalization): reference to const element.
Definition: basic_blob.hpp:308
boost::asio::mutable_buffer mutable_buffer()
Same as const_buffer() but the returned view is mutable.
Iterator erase(Const_iterator first, Const_iterator past_last)
Performs the minimal number of operations to make range [begin(), end()) unchanged except for lacking...
Iterator iterator_sans_const(Const_iterator it)
Returns iterator-to-mutable equivalent to given iterator-to-immutable.
#define FLOW_LOG_TRACE_WITHOUT_CHECKING(ARG_stream_fragment)
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:422
#define FLOW_LOG_SET_CONTEXT(ARG_logger_ptr, ARG_component_payload)
For the rest of the block within which this macro is instantiated, causes all FLOW_LOG_....
Definition: log.hpp:473
#define FLOW_LOG_TRACE(ARG_stream_fragment)
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:230
@ S_TRACE
Message indicates any condition that may occur with great frequency (thus verbose if logged).
Flow module containing miscellaneous general-use facilities that don't fit into any other Flow module...
bool in_closed_open_range(T const &min_val, T const &val, T const &max_val)
Returns true if and only if the given value is within the given range, given as a [low,...
Definition: util.hpp:497
bool blobs_sharing(const Basic_blob< Allocator_t, SHARING > &blob1, const Basic_blob< Allocator_t, SHARING > &blob2)
Returns true if and only if both given objects are not zero() == true, and they either co-own a commo...
void swap(Basic_blob< Allocator_t, SHARING > &blob1, Basic_blob< Allocator_t, SHARING > &blob2, log::Logger *logger_ptr) noexcept
Equivalent to blob1.swap(blob2).
bool in_closed_range(T const &min_val, T const &val, T const &max_val)
Returns true if and only if the given value is within the given range, inclusive.
Definition: util.hpp:481
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:627
unsigned char uint8_t
Byte. Best way to represent a byte of binary data. This is 8 bits on all modern systems.
Definition: common.hpp:380
Tag type used at least in Basic_blob and Blob_with_log_context to specify that an allocated buffer be...
Definition: basic_blob.hpp:40