DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
AccountedAllocator.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
10#include <cstddef>
11#include <limits>
12#include <memory>
13#include <new>
14#include <type_traits>
15
17{
18
20{
21 void *context = nullptr;
22 void (*request)(void *, std::size_t) = nullptr;
23 void *(*allocate)(void *, std::size_t, std::size_t) = nullptr;
24 void (*deallocate)(void *, void *, std::size_t, std::size_t) = nullptr;
25
26 void charge(std::size_t bytes) const
27 {
28 if (request)
29 request(context, bytes);
30 }
31};
32
33// Charges the actual requested payload before allocating. Accounting may reject
34// the request by throwing; the underlying allocator is then never called. This
35// is cumulative requested payload, not live bytes, so freeing does not refund it.
36// The account must outlive its containers. A custom allocation source supplies
37// both callbacks and accounts for its own upstream payload, including metadata.
38template <typename T> class AccountedAllocator
39{
40 public:
41 using value_type = T;
43 using propagate_on_container_swap = std::true_type;
44 using is_always_equal = std::false_type;
45
46 AccountedAllocator() noexcept = default;
47 explicit AccountedAllocator(const AllocationAccount *account) noexcept : account_(account) {}
48 template <typename U>
49 AccountedAllocator(const AccountedAllocator<U> &other) noexcept : account_(other.account()) {}
50
51 [[nodiscard]] T *allocate(std::size_t count)
52 {
53 // Let the standard allocator reject an impossible element count without
54 // wrapping the byte count passed to the account.
55 if (count > std::numeric_limits<std::size_t>::max() / sizeof(T))
56 return std::allocator<T>{}.allocate(count);
57 const auto bytes = count * sizeof(T);
58 if (account_)
59 {
60 account_->charge(bytes);
61 if (account_->allocate)
62 return static_cast<T *>(account_->allocate(account_->context, bytes, alignof(T)));
63 }
64 // Use the matching allocation primitive directly: some std::allocator
65 // implementations request extra large-block alignment payload even for
66 // ordinary scalar T. The request here equals the charged payload.
67 if constexpr (alignof(T) > alignof(std::max_align_t))
68 return static_cast<T *>(::operator new(bytes, std::align_val_t{alignof(T)}));
69 else
70 return static_cast<T *>(::operator new(bytes));
71 }
72 void deallocate(T *pointer, std::size_t count) noexcept
73 {
74 if (account_ && account_->deallocate)
75 {
76 account_->deallocate(account_->context, pointer, count * sizeof(T), alignof(T));
77 return;
78 }
79 if constexpr (alignof(T) > alignof(std::max_align_t))
80 ::operator delete(pointer, std::align_val_t{alignof(T)});
81 else
82 ::operator delete(pointer);
83 }
84 [[nodiscard]] const AllocationAccount *account() const noexcept { return account_; }
85 template <typename U>
86 [[nodiscard]] bool operator==(const AccountedAllocator<U> &other) const noexcept
87 {
88 return account_ == other.account();
89 }
90
91 private:
92 const AllocationAccount *account_ = nullptr;
93};
94
95} // namespace dspark::detail
void deallocate(T *pointer, std::size_t count) noexcept
AccountedAllocator() noexcept=default
bool operator==(const AccountedAllocator< U > &other) const noexcept
AccountedAllocator(const AccountedAllocator< U > &other) noexcept
const AllocationAccount * account() const noexcept
void(* deallocate)(void *, void *, std::size_t, std::size_t)
void(* request)(void *, std::size_t)
void charge(std::size_t bytes) const
void *(* allocate)(void *, std::size_t, std::size_t)