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