File: tracking.hpp

package info (click to toggle)
foonathan-memory 0.7.4-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,644 kB
  • sloc: cpp: 12,425; xml: 139; sh: 48; makefile: 25
file content (429 lines) | stat: -rw-r--r-- 19,895 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// Copyright (C) 2015-2025 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib

#ifndef FOONATHAN_MEMORY_TRACKING_HPP_INCLUDED
#define FOONATHAN_MEMORY_TRACKING_HPP_INCLUDED

/// \file
/// Class \ref foonathan::memory::tracked_allocator and related classes and functions.

#include "detail/utility.hpp"
#include "allocator_traits.hpp"
#include "memory_arena.hpp"

namespace foonathan
{
    namespace memory
    {
        namespace detail
        {
            template <class Allocator, class Tracker>
            auto set_tracker(int, Allocator& allocator, Tracker* tracker) noexcept
                -> decltype(allocator.get_allocator().set_tracker(tracker))
            {
                return allocator.get_allocator().set_tracker(tracker);
            }
            template <class Allocator, class Tracker>
            void set_tracker(short, Allocator&, Tracker*) noexcept
            {
            }

            // used with deeply_tracked_allocator
            template <class Tracker, class BlockAllocator>
            class deeply_tracked_block_allocator : FOONATHAN_EBO(BlockAllocator)
            {
            public:
                template <typename... Args>
                deeply_tracked_block_allocator(std::size_t block_size, Args&&... args)
                : BlockAllocator(block_size, detail::forward<Args>(args)...), tracker_(nullptr)
                {
                }

                memory_block allocate_block()
                {
                    auto block = BlockAllocator::allocate_block();
                    if (tracker_) // on first call tracker_ is nullptr
                        tracker_->on_allocator_growth(block.memory, block.size);
                    return block;
                }

                void deallocate_block(memory_block block) noexcept
                {
                    if (tracker_) // on last call tracker_ is nullptr again
                        tracker_->on_allocator_shrinking(block.memory, block.size);
                    BlockAllocator::deallocate_block(block);
                }

                std::size_t next_block_size() const noexcept
                {
                    return BlockAllocator::next_block_size();
                }

                void set_tracker(Tracker* tracker) noexcept
                {
                    tracker_ = tracker;
                }

            private:
                Tracker* tracker_;
            };
        } // namespace detail

        /// A \concept{concept_blockallocator,BlockAllocator} adapter that tracks another allocator using a \concept{concept_tracker,tracker}.
        /// It wraps another \concept{concept_blockallocator,BlockAllocator} and calls the tracker function before forwarding to it.
        /// The class can then be used anywhere a \concept{concept_blockallocator,BlockAllocator} is required and the memory usage will be tracked.<br>
        /// It will only call the <tt>on_allocator_growth()</tt> and <tt>on_allocator_shrinking()</tt> tracking functions,
        /// since a \concept{concept_blockallocator,BlockAllocator} is normally used inside higher allocators only.
        /// \ingroup adapter
        template <class Tracker, class BlockOrRawAllocator>
        class tracked_block_allocator
        : FOONATHAN_EBO(Tracker, make_block_allocator_t<BlockOrRawAllocator>)
        {
        public:
            using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
            using tracker        = Tracker;

            /// @{
            /// \effects Creates it by giving it a \concept{concept_tracker,tracker} and the tracked \concept{concept_rawallocator,RawAllocator}.
            /// It will embed both objects.
            explicit tracked_block_allocator(tracker t = {}) noexcept : tracker(detail::move(t)) {}

            tracked_block_allocator(tracker t, allocator_type&& alloc) noexcept
            : tracker(detail::move(t)), allocator_type(detail::move(alloc))
            {
            }
            /// @}

            /// \effects Creates it in the form required by the concept.
            /// The allocator will be constructed using \c block_size and \c args.
            template <typename... Args>
            tracked_block_allocator(std::size_t block_size, tracker t, Args&&... args)
            : tracker(detail::move(t)), allocator_type(block_size, detail::forward<Args>(args)...)
            {
            }

            /// \effects Calls <tt>Tracker::on_allocator_growth()</tt> after forwarding to the allocator.
            /// \returns The block as the returned by the allocator.
            memory_block allocate_block()
            {
                auto block = allocator_type::allocate_block();
                this->on_allocator_growth(block.memory, block.size);
                return block;
            }

            /// \effects Calls <tt>Tracker::on_allocator_shrinking()</tt> and forwards to the allocator.
            void deallocate_block(memory_block block) noexcept
            {
                this->on_allocator_shrinking(block.memory, block.size);
                allocator_type::deallocate_block(block);
            }

            /// \returns The next block size as returned by the allocator.
            std::size_t next_block_size() const noexcept
            {
                return allocator_type::next_block_size();
            }

            /// @{
            /// \returns A (const) reference to the used allocator.
            allocator_type& get_allocator() noexcept
            {
                return *this;
            }

            const allocator_type& get_allocator() const noexcept
            {
                return *this;
            }
            /// @}

            /// @{
            /// \returns A (const) reference to the tracker.
            tracker& get_tracker() noexcept
            {
                return *this;
            }

            const tracker& get_tracker() const noexcept
            {
                return *this;
            }
            /// @}
        };

        /// Similar to \ref tracked_block_allocator, but shares the tracker with the higher level allocator.
        /// This allows tracking both (de-)allocations and growth with one tracker.
        /// \note Due to implementation reasons, it cannot track growth and shrinking in the constructor/destructor of the higher level allocator.
        /// \ingroup adapter
        template <class Tracker, class BlockOrRawAllocator>
        using deeply_tracked_block_allocator = FOONATHAN_IMPL_DEFINED(
            detail::deeply_tracked_block_allocator<Tracker,
                                                   make_block_allocator_t<BlockOrRawAllocator>>);

        /// A \concept{concept_rawallocator,RawAllocator} adapter that tracks another allocator using a \concept{concept_tracker,tracker}.
        /// It wraps another \concept{concept_rawallocator,RawAllocator} and calls the tracker function before forwarding to it.
        /// The class can then be used anywhere a \concept{concept_rawallocator,RawAllocator} is required and the memory usage will be tracked.<br>
        /// If the \concept{concept_rawallocator,RawAllocator} uses \ref deeply_tracked_block_allocator as \concept{concept_blockallocator,BlockAllocator},
        /// it will also track growth and shrinking of the allocator.
        /// \ingroup adapter
        template <class Tracker, class RawAllocator>
        class tracked_allocator
        : FOONATHAN_EBO(Tracker, allocator_traits<RawAllocator>::allocator_type)
        {
            using traits            = allocator_traits<RawAllocator>;
            using composable_traits = composable_allocator_traits<RawAllocator>;

        public:
            using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
            using tracker        = Tracker;

            using is_stateful = std::integral_constant<bool, traits::is_stateful::value
                                                                 || !std::is_empty<Tracker>::value>;

            /// @{
            /// \effects Creates it by giving it a \concept{concept_tracker,tracker} and the tracked \concept{concept_rawallocator,RawAllocator}.
            /// It will embed both objects.
            /// \note This will never call the <tt>Tracker::on_allocator_growth()</tt> function.
            explicit tracked_allocator(tracker t = {}) noexcept
            : tracked_allocator(detail::move(t), allocator_type{})
            {
            }

            tracked_allocator(tracker t, allocator_type&& allocator) noexcept
            : tracker(detail::move(t)), allocator_type(detail::move(allocator))
            {
                detail::set_tracker(0, get_allocator(), &get_tracker());
            }
            /// @}

            /// \effects Destroys both tracker and allocator.
            /// \note This will never call the <tt>Tracker::on_allocator_shrinking()</tt> function.
            ~tracked_allocator() noexcept
            {
                detail::set_tracker(0, get_allocator(), static_cast<tracker*>(nullptr));
            }

            /// @{
            /// \effects Moving moves both the tracker and the allocator.
            tracked_allocator(tracked_allocator&& other) noexcept
            : tracker(detail::move(other)), allocator_type(detail::move(other))
            {
                detail::set_tracker(0, get_allocator(), &get_tracker());
            }

            tracked_allocator& operator=(tracked_allocator&& other) noexcept
            {
                tracker::operator=(detail::move(other));
                allocator_type::operator=(detail::move(other));
                detail::set_tracker(0, get_allocator(), &get_tracker());
                return *this;
            }
            /// @}

            /// \effects Calls <tt>Tracker::on_node_allocation()</tt> and forwards to the allocator.
            /// If a growth occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_growth()</tt>.
            /// \returns The result of <tt>allocate_node()</tt>
            void* allocate_node(std::size_t size, std::size_t alignment)
            {
                auto mem = traits::allocate_node(get_allocator(), size, alignment);
                this->on_node_allocation(mem, size, alignment);
                return mem;
            }

            /// \effects Calls the composable node allocation function.
            /// If allocation was successful, also calls `Tracker::on_node_allocation()`.
            /// \returns The result of `try_allocate_node()`.
            void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
            {
                auto mem = composable_traits::try_allocate_node(get_allocator(), size, alignment);
                if (mem)
                    this->on_node_allocation(mem, size, alignment);
                return mem;
            }

            /// \effects Calls <tt>Tracker::on_array_allocation()</tt> and forwards to the allocator.
            /// If a growth occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_growth()</tt>.
            /// \returns The result of <tt>allocate_array()</tt>
            void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
            {
                auto mem = traits::allocate_array(get_allocator(), count, size, alignment);
                this->on_array_allocation(mem, count, size, alignment);
                return mem;
            }

            /// \effects Calls the composable array allocation function.
            /// If allocation was succesful, also calls `Tracker::on_array_allocation()`.
            /// \returns The result of `try_allocate_array()`.
            void* try_allocate_array(std::size_t count, std::size_t size,
                                     std::size_t alignment) noexcept
            {
                auto mem =
                    composable_traits::try_allocate_array(get_allocator(), count, size, alignment);
                if (mem)
                    this->on_array_allocation(mem, count, size, alignment);
                return mem;
            }

            /// \effects Calls <tt>Tracker::on_node_deallocation()</tt> and forwards to the allocator's <tt>deallocate_node()</tt>.
            /// If shrinking occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_shrinking()</tt>.
            void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
            {
                this->on_node_deallocation(ptr, size, alignment);
                traits::deallocate_node(get_allocator(), ptr, size, alignment);
            }

            /// \effects Calls the composable node deallocation function.
            /// If it was succesful, also calls `Tracker::on_node_deallocation()`.
            /// \returns The result of `try_deallocate_node()`.
            bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
            {
                auto res =
                    composable_traits::try_deallocate_node(get_allocator(), ptr, size, alignment);
                if (res)
                    this->on_node_deallocation(ptr, size, alignment);
                return res;
            }

            /// \effects Calls <tt>Tracker::on_array_deallocation()</tt> and forwards to the allocator's <tt>deallocate_array()</tt>.
            /// If shrinking occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_shrinking()</tt>.
            void deallocate_array(void* ptr, std::size_t count, std::size_t size,
                                  std::size_t alignment) noexcept
            {
                this->on_array_deallocation(ptr, count, size, alignment);
                traits::deallocate_array(get_allocator(), ptr, count, size, alignment);
            }

            /// \effects Calls the composable array deallocation function.
            /// If it was succesful, also calls `Tracker::on_array_deallocation()`.
            /// \returns The result of `try_deallocate_array()`.
            bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
                                      std::size_t alignment) noexcept
            {
                auto res = composable_traits::try_deallocate_array(ptr, count, size, alignment);
                if (res)
                    this->on_array_deallocation(ptr, count, size, alignment);
                return res;
            }

            /// @{
            /// \returns The result of the corresponding function on the wrapped allocator.
            std::size_t max_node_size() const
            {
                return traits::max_node_size(get_allocator());
            }

            std::size_t max_array_size() const
            {
                return traits::max_array_size(get_allocator());
            }

            std::size_t max_alignment() const
            {
                return traits::max_alignment(get_allocator());
            }
            /// @}

            /// @{
            /// \returns A (\c const) reference to the wrapped allocator.
            allocator_type& get_allocator() noexcept
            {
                return *this;
            }

            const allocator_type& get_allocator() const noexcept
            {
                return *this;
            }
            /// @}

            /// @{
            /// \returns A (\c const) reference to the tracker.
            tracker& get_tracker() noexcept
            {
                return *this;
            }

            const tracker& get_tracker() const noexcept
            {
                return *this;
            }
            /// @}
        };

        /// \effects Takes a \concept{concept_rawallocator,RawAllocator} and wraps it with a \concept{concept_tracker,tracker}.
        /// \returns A \ref tracked_allocator with the corresponding parameters forwarded to the constructor.
        /// \relates tracked_allocator
        template <class Tracker, class RawAllocator>
        auto make_tracked_allocator(Tracker t, RawAllocator&& alloc)
            -> tracked_allocator<Tracker, typename std::decay<RawAllocator>::type>
        {
            return tracked_allocator<Tracker, typename std::decay<RawAllocator>::type>{detail::move(
                                                                                           t),
                                                                                       detail::move(
                                                                                           alloc)};
        }

        namespace detail
        {
            template <typename T, bool Block>
            struct is_block_or_raw_allocator_impl : std::true_type
            {
            };

            template <typename T>
            struct is_block_or_raw_allocator_impl<T, false> : memory::is_raw_allocator<T>
            {
            };

            template <typename T>
            struct is_block_or_raw_allocator
            : is_block_or_raw_allocator_impl<T, memory::is_block_allocator<T>::value>
            {
            };

            template <class RawAllocator, class BlockAllocator>
            struct rebind_block_allocator;

            template <template <typename...> class RawAllocator, typename... Args,
                      class OtherBlockAllocator>
            struct rebind_block_allocator<RawAllocator<Args...>, OtherBlockAllocator>
            {
                using type =
                    RawAllocator<typename std::conditional<is_block_or_raw_allocator<Args>::value,
                                                           OtherBlockAllocator, Args>::type...>;
            };

            template <class Tracker, class RawAllocator>
            using deeply_tracked_block_allocator_for =
                memory::deeply_tracked_block_allocator<Tracker,
                                                       typename RawAllocator::allocator_type>;

            template <class Tracker, class RawAllocator>
            using rebound_allocator = typename rebind_block_allocator<
                RawAllocator, deeply_tracked_block_allocator_for<Tracker, RawAllocator>>::type;
        } // namespace detail

        /// A \ref tracked_allocator that has rebound any \concept{concept_blockallocator,BlockAllocator} to the corresponding \ref deeply_tracked_block_allocator.
        /// This makes it a deeply tracked allocator.<br>
        /// It replaces each template argument of the given \concept{concept_rawallocator,RawAllocator} for which \ref is_block_allocator or \ref is_raw_allocator is \c true with a \ref deeply_tracked_block_allocator.
        /// \ingroup adapter
        template <class Tracker, class RawAllocator>
        FOONATHAN_ALIAS_TEMPLATE(
            deeply_tracked_allocator,
            tracked_allocator<Tracker, detail::rebound_allocator<Tracker, RawAllocator>>);

        /// \effects Takes a \concept{concept_rawallocator,RawAllocator} and deeply wraps it with a \concept{concept_tracker,tracker}.
        /// \returns A \ref deeply_tracked_allocator with the corresponding parameters forwarded to the constructor.
        /// \relates deeply_tracked_allocator
        template <class RawAllocator, class Tracker, typename... Args>
        auto make_deeply_tracked_allocator(Tracker t, Args&&... args)
            -> deeply_tracked_allocator<Tracker, RawAllocator>
        {
            return deeply_tracked_allocator<Tracker, RawAllocator>(detail::move(t),
                                                                   {detail::forward<Args>(
                                                                       args)...});
        }
    } // namespace memory
} // namespace foonathan

#endif // FOONATHAN_MEMORY_TRACKING_HPP_INCLUDED