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