Flow 2.0.0
Flow project: Full implementation reference.
default_init_allocator.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 <memory>
23#include <new>
24#include <type_traits>
25#include <utility>
26
27namespace flow::util
28{
29
30/**
31 * Allocator adaptor (useful for, e.g., `vector` that skips zero-filling) that turns a value-initialization
32 * `T()` into a default-initialization; this matters for those types (including all PoDs) for which the two differ.
33 *
34 * ### Rationale (why) ###
35 * Suppose you have `vector<uint8_t> v(10);`. This will create a buffer and fill with at least 10 zeroes.
36 * What if you want it *not* to do this? Maybe you don't want it because performance.
37 * Actually, consider an even sneakier situation that involves correctness, not just perf. Suppose you
38 * have `vector<uint8_t> v` with `.capacity() == 10` and `.size() == 0`, but you're letting some 3rd-party code
39 * operate in range `[v.begin(), v.begin() + v.capacity())`, and you're using `.size()` as a store of the result
40 * of the 3rd-party operation. Say the 3rd party (e.g., a builder library) performs its op (serialization)
41 * and reports that it has used exactly 7 bytes. Now you call `v.resize(7)`. The problem: this will fill the first
42 * 7 bytes of the range with zeroes, wiping out the builder's work. Of course you could have solved this
43 * by keeping `.capacity() == .size() == 10` at all times and marking down the serialization's size in a separate
44 * variable. That's a bit sad though: you have a perfectly nice potential holder of the size, the `vector` inner
45 * `m_size` (or whatever it is called), but you cannot use it due to the value-initializing behavior of `vector`.
46 *
47 * The link
48 * https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
49 * contains a nice solution in the form of an allocator adaptor which can be set as the `Allocator` template param
50 * of a `vector` (etc.), adapting the normal allocator used; whether that's the typical heap-allocating `std::allocator`
51 * or something more exotic (e.g., SHM-allocating `ipc::shm::stl::Stateless_allocator` from the sister project
52 * Flow-IPC). cppreference.com `vector::resize()` page even links to that in "Notes" about this problem!
53 *
54 * ### What it does (what/how) ###
55 * The allocator works as follows: Suppose the STL-compliant container, having raw-allocated a buffer `sizeof(T)`
56 * bytes long, at `void* p`, requests to in-place-construct a `T{}` (a/k/a `T()`) at `p`. That is what, e.g.,
57 * `vector::resize(size_t)` does: it *value-initializes* the `T` at address `p`. Now there are two possibilities:
58 * - `T` has no user-provided default constructor (e.g., it is a PoD (plain old data-type) or an aggregate
59 * like `struct { std::string m_s; int m_i; }`): value-initialization `T t{};` *does not equal*
60 * default-initialization `T t;`. The latter leaves pre-existing (possibly garbage) values in `t` (or the
61 * relevant parts of it). The former first zero-initializes `t`; e.g., if `T` is `int` then it zeroes `t`.
62 * - `T` has a user-provided default constructor: value-initialization `T t{}` *does* equal default-initialization
63 * `T t`. They will both default-construct `T` via `T::T()`.
64 *
65 * The allocator adaptor changes the *former* bullet point to act like the *latter* bullet point;
66 * while making no changes for the latter `T`s. For the former `T`s:
67 * The container will ask to do `T t{}` (a/k/a `T t = T()`), but what will happen in reality is `T t;` instead.
68 * Or, in plainer English, it's gonna make it so that `int`s and `float`s and simple `struct`s and stuff
69 * don't get auto-zeroed by containers when default-creating them on one's behalf.
70 *
71 * ### What about Blob? ###
72 * Blob specifically exists -- among a few other reasons -- to avoid the default-zeroing that a `vector<uint8_t>`
73 * would do. You should use it instead of `vector<uint8_t>`; you'll get quite a few goodies as a result.
74 * Moreover Blob is the heap-allocating case of Basic_blob, which plays well with custom (e.g., SHM-allocating)
75 * allocators. Therefore Default_init_allocator is not needed for byte buffers; it remains potentially useful for
76 * containers of other types.
77 *
78 * ### Source ###
79 * I (ygoldfel) took it from the above link(s), made stylistic edits, and added documentation.
80 *
81 * @tparam T
82 * The type managed by this instance of the allocator.
83 * See `Allocator` concept (in standard or cppreference.com).
84 * @tparam Allocator
85 * The `Allocator` being adapted. Usually one uses the heap-allocating `std::allocator` which is the default
86 * arg for STL-compliant containers usually. However it may well be something more advanced
87 * such as Flow-IPC's SHM-allocating `ipc::shm::stl::Stateless_allocator` or alias
88 * `ipc::shm::classic::Pool_arena::Allocator`.
89 */
90template <typename T, typename Allocator>
92{
93public:
94 // Types.
95
96 /**
97 * Satisfies `Allocator` concept optional requirement for rebinding to another type `U`: the result is
98 * a Default_init_allocator adapting the adaptee allocator rebound to `U`. Without this `std::allocator_traits`
99 * would use the adaptee's own `rebind` (if any; e.g., `std::allocator` in C++17), silently dropping the
100 * adaptor; and containers rebind routinely (e.g., `vector` to its own element type; `list` to its node type).
101 *
102 * @tparam U
103 * See `Allocator` concept docs.
104 */
105 template<typename U>
106 struct rebind
107 {
108 // Types.
109
110 /// See `Allocator` concept docs.
112 };
113
114 // Constructors/destructor.
115
116 /// Inherit adaptee allocator's constructors.
117 using Allocator::Allocator;
118
119 // Methods.
120
121 /**
122 * Satisfies `Allocator` concept optional requirement for in-place construction: specialized for 0-args,
123 * i.e., value-initialization; replaces value-initialization with default-initialization.
124 * This specialization is the reason Default_init_allocator exists at all.
125 *
126 * @tparam U
127 * Type being constructed. See `Allocator` concept docs.
128 * @param ptr
129 * Address at which to in-place-construct the `U`.
130 */
131 template<typename U>
132 void construct(U* ptr) noexcept(std::is_nothrow_default_constructible_v<U>);
133
134 /**
135 * Satisfies `Allocator` concept optional requirement for in-place construction: non-specialized version
136 * invoked for 1+ args. This behaves identically to the adaptee allocator.
137 *
138 * @tparam U
139 * Type being constructed. See `Allocator` concept docs.
140 * @tparam Args
141 * Constructor args (1+ of them).
142 * @param ptr
143 * Address at which to in-place-construct the `U`.
144 * @param args
145 * See `Args...`.
146 */
147 template<typename U, typename... Args>
148 void construct(U* ptr, Args&&... args);
149}; // class Default_init_allocator
150
151// Template implementations.
152
153template<typename T, typename Allocator>
154template <typename U>
155void Default_init_allocator<T, Allocator>::construct(U* ptr) noexcept(std::is_nothrow_default_constructible_v<U>)
156{
157 // If U's default-init = value-init, this changes no behavior. Otherwise this performs default-init (~no-op).
158 ::new(static_cast<void*>(ptr)) U;
159}
160
161template<typename T, typename Allocator>
162template <typename U, typename... Args>
164{
165 // Just do the normal thing.
166 std::allocator_traits<Allocator>::construct(static_cast<Allocator&>(*this),
167 ptr, std::forward<Args>(args)...);
168}
169
170} // namespace flow::util
Allocator adaptor (useful for, e.g., vector that skips zero-filling) that turns a value-initializatio...
void construct(U *ptr) noexcept(std::is_nothrow_default_constructible_v< U >)
Satisfies Allocator concept optional requirement for in-place construction: specialized for 0-args,...
Flow module containing miscellaneous general-use facilities that don't fit into any other Flow module...
Satisfies Allocator concept optional requirement for rebinding to another type U: the result is a Def...