|
| 1 | +========================= |
| 2 | +C++ Type Aware Allocators |
| 3 | +========================= |
| 4 | + |
| 5 | +.. contents:: |
| 6 | + :local: |
| 7 | + |
| 8 | +Introduction |
| 9 | +============ |
| 10 | + |
| 11 | +Clang includes an implementation of P2719 "Type-aware allocation and deallocation |
| 12 | +functions". |
| 13 | + |
| 14 | +This is a feature that extends the semantics of `new`, `new[]`, `delete` and |
| 15 | +`delete[]` operators to expose the type being allocated to the operator. This |
| 16 | +can be used to customize allocation of types without needing to modify the |
| 17 | +type declaration, or via template definitions fully generic type aware |
| 18 | +allocators. |
| 19 | + |
| 20 | +P2719 introduces a type-identity tag as valid parameter type for all allocation |
| 21 | +operators. This tag is a default initialized value of type `std::type_identity<T>` |
| 22 | +where T is the type being allocated or deallocated. Unlike the other placement |
| 23 | +arguments this tag is passed as the first parameter to the operator. |
| 24 | + |
| 25 | +The most basic use case is as follows |
| 26 | + |
| 27 | +.. code-block:: c++ |
| 28 | + |
| 29 | + #include <new> |
| 30 | + #include <type_traits> |
| 31 | + |
| 32 | + struct S { |
| 33 | + // ... |
| 34 | + }; |
| 35 | + |
| 36 | + void *operator new(std::type_identity<S>, size_t, std::align_val_t); |
| 37 | + void operator delete(std::type_identity<S>, void *, size_t, std::align_val_t); |
| 38 | +
|
| 39 | + void f() { |
| 40 | + S *s = new S; // calls ::operator new(std::type_identity<S>(), sizeof(S), alignof(S)) |
| 41 | + delete s; // calls ::operator delete(std::type_identity<S>(), s, sizeof(S), alignof(S)) |
| 42 | + } |
| 43 | +
|
| 44 | +While this functionality alone is powerful and useful, the true power comes |
| 45 | +by using templates. In addition to adding the type-identity tag, P2719 allows |
| 46 | +the tag parameter to be a dependent specialization of `std::type_identity`, |
| 47 | +updates the overload resolution rules to support full template deduction and |
| 48 | +constraint semantics, and updates the definition of usual deallocation functions |
| 49 | +to include `operator delete` definitions that are templatized on the |
| 50 | +type-identity tag. |
| 51 | + |
| 52 | +This allows arbitrarily constrained definitions of the operators that resolve |
| 53 | +as would be expected for any other template function resolution, e.g (only |
| 54 | +showing `operator new` for brevity) |
| 55 | + |
| 56 | +.. code-block:: c++ |
| 57 | + |
| 58 | + template <typename T, unsigned Size> struct Array { |
| 59 | + T buffer[Size]; |
| 60 | + }; |
| 61 | + |
| 62 | + // Starting with a concrete type |
| 63 | + void *operator new(std::type_identity<Array<int, 5>>, size_t, std::align_val_t); |
| 64 | +
|
| 65 | + // Only care about five element arrays |
| 66 | + template <typename T> |
| 67 | + void *operator new(std::type_identity<Array<T, 5>>, size_t, std::align_val_t); |
| 68 | +
|
| 69 | + // An array of N floats |
| 70 | + template <unsigned N> |
| 71 | + void *operator new(std::type_identity<Array<float, N>>, size_t, std::align_val_t); |
| 72 | +
|
| 73 | + // Any array |
| 74 | + template <typename T, unsigned N> |
| 75 | + void *operator new(std::type_identity<Array<T, N>>, size_t, std::align_val_t); |
| 76 | +
|
| 77 | + // A handy concept |
| 78 | + template <typename T> concept Polymorphic = std::is_polymorphic_v<T>; |
| 79 | + |
| 80 | + // Only applies is T is Polymorphic |
| 81 | + template <Polymorphic T, unsigned N> |
| 82 | + void *operator new(std::type_identity<Array<T, N>>, size_t, std::align_val_t); |
| 83 | +
|
| 84 | + // Any even length array |
| 85 | + template <typename T, unsigned N> |
| 86 | + void *operator new(std::type_identity<Array<T, N>>, size_t, std::align_val_t) |
| 87 | + requires(N%2 == 0); |
| 88 | +
|
| 89 | +Operator selection then proceeds according to the usual rules for choosing |
| 90 | +the best/most constrained match. |
| 91 | + |
| 92 | +Any declaration of a type aware operator new or operator delete must include a |
| 93 | +matching complimentary operator defined in the same scope. |
| 94 | + |
| 95 | +Notes |
| 96 | +===== |
| 97 | + |
| 98 | +Unconstrained Global Operators |
| 99 | +------------------------------ |
| 100 | + |
| 101 | +Declaring an unconstrained type aware global operator `new` or `delete` (or |
| 102 | +`[]` variants) creates numerous hazards, similar to, but different from, those |
| 103 | +created by attempting to replace the non-type aware global operators. For that |
| 104 | +reason unconstrained operators are strongly discouraged. |
| 105 | + |
| 106 | +Mismatching Constraints |
| 107 | +----------------------- |
| 108 | + |
| 109 | +When declaring global type aware operators you should ensure the constraints |
| 110 | +applied to new and delete match exactly, and declare them together. This |
| 111 | +limits the risk of having mismatching operators selected due to differing |
| 112 | +constraints resulting in changes to prioritization when determining the most |
| 113 | +viable candidate. |
| 114 | + |
| 115 | +Declarations Across Libraries |
| 116 | +----------------------------- |
| 117 | + |
| 118 | +Declaring a typed allocator for a type in a separate TU or library creates |
| 119 | +similar hazards as different libraries and TUs may see (or select) different |
| 120 | +definitions. |
| 121 | + |
| 122 | +Under this model something like this would be risky |
| 123 | + |
| 124 | +.. code-block:: c++ |
| 125 | + |
| 126 | + template<typename T> |
| 127 | + void *operator new(std::type_identity<std::vector<T>>, size_t, std::align_val_t); |
| 128 | +
|
| 129 | +However this hazard is not present simply due to the use of the a type from |
| 130 | +another library: |
| 131 | + |
| 132 | +.. code-block:: c++ |
| 133 | + |
| 134 | + template<typename T> |
| 135 | + struct MyType { |
| 136 | + T thing; |
| 137 | + }; |
| 138 | + template<typename T> |
| 139 | + void *operator new(std::type_identity<MyType<std::vector<T>>>, size_t, std::align_val_t); |
| 140 | +
|
| 141 | +Here we see `std::vector` being used, but that is not the actual type being |
| 142 | +allocated. |
| 143 | + |
| 144 | +Implicit and Placement Parameters |
| 145 | +--------------------------------- |
| 146 | + |
| 147 | +Type aware allocators are always passed both the implicit alignment and size |
| 148 | +parameters in all cases. Explicit placement parameters are supported after the |
| 149 | +mandatory implicit parameters. |
| 150 | + |
| 151 | +Publication |
| 152 | +=========== |
| 153 | + |
| 154 | +`Type-aware allocation and deallocation functions <https://wg21.link/P2719>`_. |
| 155 | +Louis Dionne, Oliver Hunt. |
0 commit comments