userver: userver/utils/any_storage.hpp Source File
Loading...
Searching...
No Matches
any_storage.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/any_storage.hpp
4/// @brief @copybrief utils::AnyStorage
5
6#include <cstddef>
7#include <memory>
8#include <new>
9#include <stdexcept>
10#include <type_traits>
11#include <vector>
12
13#include <userver/compiler/impl/lifetime.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace utils {
18
19namespace any_storage::impl {
20
21using Offset = std::size_t;
22
23template <typename StorageTag>
24inline Offset data_offset{0};
25
26template <typename StorageTag>
27inline std::size_t count{0};
28
29template <typename StorageTag>
30Offset RegisterData(std::size_t size, std::size_t alignment) noexcept {
31 data_offset<StorageTag> += (alignment - (data_offset<StorageTag> % alignment)) % alignment;
32 const Offset result = data_offset<StorageTag>;
33 data_offset<StorageTag> += size;
34
35 count<StorageTag> ++;
36 return result;
37}
38
39void AssertStaticRegistrationAllowed();
40
41template <typename T>
42void Delete(std::byte* data) noexcept {
43 std::destroy_at(reinterpret_cast<T*>(data));
44}
45
46} // namespace any_storage::impl
47
48template <typename StorageTag>
49class AnyStorage;
50
51template <typename StorageTag, typename Data>
52class AnyStorageDataTag final {
53public:
54 AnyStorageDataTag() noexcept
55 : number_(any_storage::impl::count<StorageTag>),
56 offset_(any_storage::impl::RegisterData<StorageTag>(sizeof(Data), alignof(Data))) {
57 static_assert(!std::is_reference_v<Data>);
58 static_assert(!std::is_const_v<Data>);
59 static_assert(
60 __STDCPP_DEFAULT_NEW_ALIGNMENT__ >= alignof(Data),
61 "Overaligned data members are not supported by AnyStorage"
62 );
63
64 any_storage::impl::AssertStaticRegistrationAllowed();
65 }
66
67private:
68 const std::size_t number_;
69 const any_storage::impl::Offset offset_;
70
71 friend class AnyStorage<StorageTag>;
72};
73
74/// @ingroup userver_universal userver_containers
75///
76/// @brief map-like heterogeneous data storage
77///
78/// ## Usage example
79/// @snippet utils/any_storage_test.cpp AnyStorage
80template <typename StorageTag>
81class AnyStorage final {
82public:
83 AnyStorage();
84
85 AnyStorage(AnyStorage&& other) noexcept = default;
86 AnyStorage& operator=(AnyStorage&& other) noexcept;
87 ~AnyStorage();
88
89 /// @returns Stored data.
90 template <typename Data>
91 const Data& Get(const AnyStorageDataTag<StorageTag, Data>& tag) const USERVER_IMPL_LIFETIME_BOUND;
92
93 /// @returns Stored data.
94 /// @throws std::runtime_error if no data was stored
95 template <typename Data>
96 Data& Get(const AnyStorageDataTag<StorageTag, Data>& tag) USERVER_IMPL_LIFETIME_BOUND;
97
98 /// @brief Stores the data.
99 template <typename Data>
100 Data& Set(AnyStorageDataTag<StorageTag, Data> tag, Data data) USERVER_IMPL_LIFETIME_BOUND;
101
102 /// @brief Emplaces the data. The data is rewritten if
103 /// already stored.
104 template <typename Data, typename... Args>
105 Data& Emplace(const AnyStorageDataTag<StorageTag, Data>& tag, Args&&... args) USERVER_IMPL_LIFETIME_BOUND;
106
107 /// @returns Pointer to stored data or nullptr if
108 /// no data is found.
109 template <typename Data>
110 Data* GetOptional(const AnyStorageDataTag<StorageTag, Data>& tag) noexcept USERVER_IMPL_LIFETIME_BOUND;
111
112 /// @returns Pointer to stored data or nullptr if
113 /// no data found.
114 template <typename Data>
115 const Data* GetOptional(const AnyStorageDataTag<StorageTag, Data>& tag) const noexcept USERVER_IMPL_LIFETIME_BOUND;
116
117 /// @brief Erase data.
118 template <typename Data>
119 void Erase(const AnyStorageDataTag<StorageTag, Data>& tag);
120
121private:
122 struct AllocRecord {
123 void (*deleter)(std::byte*) noexcept;
124 std::size_t offset;
125 };
126
127 AllocRecord* GetRecords() noexcept;
128
129 static any_storage::impl::Offset CalcOffset() noexcept;
130
131 void Destroy() noexcept;
132
133 std::unique_ptr<std::byte[]> raw_data_;
134};
135
136template <typename StorageTag>
137any_storage::impl::Offset AnyStorage<StorageTag>::CalcOffset() noexcept {
138 const auto offset = any_storage::impl::data_offset<StorageTag>;
139 return ((offset + alignof(AllocRecord) - 1) / alignof(AllocRecord)) * alignof(AllocRecord);
140}
141
142template <typename StorageTag>
143AnyStorage<StorageTag>::AnyStorage()
144 : raw_data_(new std::byte[CalcOffset() + (sizeof(AllocRecord) * any_storage::impl::count<StorageTag>)])
145{
146 static_assert(std::is_trivial_v<AllocRecord>);
147
148 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
149 auto records = GetRecords();
150 for (std::size_t i = 0; i < any_storage::impl::count<StorageTag>; i++) {
151 auto& record = records[i];
152 record.deleter = nullptr;
153 }
154}
155
156template <typename StorageTag>
157AnyStorage<StorageTag>& AnyStorage<StorageTag>::operator=(AnyStorage&& other) noexcept {
158 if (this != &other) {
159 Destroy();
160 raw_data_ = std::move(other.raw_data_);
161 }
162 return *this;
163}
164
165template <typename StorageTag>
166AnyStorage<StorageTag>::~AnyStorage() {
167 Destroy();
168}
169
170template <typename StorageTag>
171void AnyStorage<StorageTag>::Destroy() noexcept {
172 if (!raw_data_) {
173 // moved out data
174 return;
175 }
176
177 auto records = GetRecords();
178
179 for (std::size_t i = 0; i < any_storage::impl::count<StorageTag>; i++) {
180 auto& record = records[i];
181 if (record.deleter) {
182 record.deleter(&raw_data_[record.offset]);
183 }
184 }
185}
186
187template <typename StorageTag>
188template <typename Data>
189Data& AnyStorage<StorageTag>::Set(const AnyStorageDataTag<StorageTag, Data> tag, Data data)
190 USERVER_IMPL_LIFETIME_BOUND {
191 auto number = tag.number_;
192 if (!GetRecords()[number].deleter) {
193 return Emplace(tag, std::move(data));
194 }
195
196 auto offset = tag.offset_;
197 return *reinterpret_cast<Data*>(&raw_data_[offset]) = std::move(data);
198}
199
200template <typename StorageTag>
201template <typename Data, typename... Args>
202Data& AnyStorage<StorageTag>::Emplace(const AnyStorageDataTag<StorageTag, Data>& tag, Args&&... args)
203 USERVER_IMPL_LIFETIME_BOUND {
204 auto number = tag.number_;
205 auto& record = GetRecords()[number];
206 if (record.deleter) {
207 record.deleter(&raw_data_[tag.offset_]);
208 }
209
210 auto offset = tag.offset_;
211 auto ptr = new (&raw_data_[offset]) Data(std::forward<Args>(args)...);
212 record = {&any_storage::impl::Delete<Data>, offset};
213 return *ptr;
214}
215
216template <typename StorageTag>
217template <typename Data>
218Data& AnyStorage<StorageTag>::Get(const AnyStorageDataTag<StorageTag, Data>& tag) USERVER_IMPL_LIFETIME_BOUND {
219 auto ptr = GetOptional(tag);
220 if (ptr) {
221 return *ptr;
222 }
223 throw std::runtime_error("No data");
224}
225
226template <typename StorageTag>
227template <typename Data>
228const Data& AnyStorage<StorageTag>::Get(const AnyStorageDataTag<StorageTag, Data>& tag
229) const USERVER_IMPL_LIFETIME_BOUND {
230 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
231 return const_cast<AnyStorage<StorageTag>*>(this)->Get<Data>(tag);
232}
233
234template <typename StorageTag>
235template <typename Data>
236Data* AnyStorage<StorageTag>::GetOptional(const AnyStorageDataTag<StorageTag, Data>& tag
237) noexcept USERVER_IMPL_LIFETIME_BOUND {
238 auto number = tag.number_;
239 auto offset = tag.offset_;
240 if (!GetRecords()[number].deleter) {
241 return nullptr;
242 }
243 return reinterpret_cast<Data*>(&raw_data_[offset]);
244}
245
246template <typename StorageTag>
247template <typename Data>
248const Data* AnyStorage<StorageTag>::GetOptional(const AnyStorageDataTag<StorageTag, Data>& tag
249) const noexcept USERVER_IMPL_LIFETIME_BOUND {
250 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
251 return const_cast<AnyStorage*>(this)->GetOptional<Data>(tag);
252}
253
254template <typename StorageTag>
255typename AnyStorage<StorageTag>::AllocRecord* AnyStorage<StorageTag>::GetRecords() noexcept {
256 return reinterpret_cast<AllocRecord*>(&raw_data_[CalcOffset()]);
257}
258
259} // namespace utils
260
261USERVER_NAMESPACE_END