1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | |
10 | // <memory> |
11 | |
12 | // To allow checking that self-move works correctly. |
13 | // ADDITIONAL_COMPILE_FLAGS: -Wno-self-move |
14 | |
15 | // template<class _Alloc> |
16 | // struct __allocation_guard; |
17 | |
18 | #include <cassert> |
19 | #include <memory> |
20 | #include <type_traits> |
21 | #include <utility> |
22 | |
23 | #include "test_allocator.h" |
24 | |
25 | using A = test_allocator<int>; |
26 | |
27 | // A trimmed-down version of `test_allocator` that is copy-assignable (in general allocators don't have to support copy |
28 | // assignment). |
29 | template <class T> |
30 | struct AssignableAllocator { |
31 | using size_type = unsigned; |
32 | using difference_type = int; |
33 | using value_type = T; |
34 | using pointer = value_type*; |
35 | using const_pointer = const value_type*; |
36 | using reference = typename std::add_lvalue_reference<value_type>::type; |
37 | using const_reference = typename std::add_lvalue_reference<const value_type>::type; |
38 | |
39 | template <class U> |
40 | struct rebind { |
41 | using other = test_allocator<U>; |
42 | }; |
43 | |
44 | test_allocator_statistics* stats_ = nullptr; |
45 | |
46 | explicit AssignableAllocator(test_allocator_statistics& stats) : stats_(&stats) { |
47 | ++stats_->count; |
48 | } |
49 | |
50 | TEST_CONSTEXPR_CXX14 AssignableAllocator(const AssignableAllocator& rhs) TEST_NOEXCEPT |
51 | : stats_(rhs.stats_) { |
52 | if (stats_ != nullptr) { |
53 | ++stats_->count; |
54 | ++stats_->copied; |
55 | } |
56 | } |
57 | |
58 | TEST_CONSTEXPR_CXX14 AssignableAllocator& operator=(const AssignableAllocator& rhs) TEST_NOEXCEPT { |
59 | stats_ = rhs.stats_; |
60 | if (stats_ != nullptr) { |
61 | ++stats_->count; |
62 | ++stats_->copied; |
63 | } |
64 | |
65 | return *this; |
66 | } |
67 | |
68 | TEST_CONSTEXPR_CXX14 pointer allocate(size_type n, const void* = nullptr) { |
69 | if (stats_ != nullptr) { |
70 | ++stats_->alloc_count; |
71 | } |
72 | return std::allocator<value_type>().allocate(n); |
73 | } |
74 | |
75 | TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) { |
76 | if (stats_ != nullptr) { |
77 | --stats_->alloc_count; |
78 | } |
79 | std::allocator<value_type>().deallocate(p, s); |
80 | } |
81 | |
82 | TEST_CONSTEXPR size_type max_size() const TEST_NOEXCEPT { return UINT_MAX / sizeof(T); } |
83 | |
84 | template <class U> |
85 | TEST_CONSTEXPR_CXX20 void construct(pointer p, U&& val) { |
86 | if (stats_ != nullptr) |
87 | ++stats_->construct_count; |
88 | #if TEST_STD_VER > 17 |
89 | std::construct_at(std::to_address(p), std::forward<U>(val)); |
90 | #else |
91 | ::new (static_cast<void*>(p)) T(std::forward<U>(val)); |
92 | #endif |
93 | } |
94 | |
95 | TEST_CONSTEXPR_CXX14 void destroy(pointer p) { |
96 | if (stats_ != nullptr) { |
97 | ++stats_->destroy_count; |
98 | } |
99 | p->~T(); |
100 | } |
101 | }; |
102 | |
103 | // Move-only. |
104 | static_assert(!std::is_copy_constructible<std::__allocation_guard<A> >::value, "" ); |
105 | static_assert(std::is_move_constructible<std::__allocation_guard<A> >::value, "" ); |
106 | static_assert(!std::is_copy_assignable<std::__allocation_guard<A> >::value, "" ); |
107 | static_assert(std::is_move_assignable<std::__allocation_guard<A> >::value, "" ); |
108 | |
109 | int main(int, char**) { |
110 | const int size = 42; |
111 | |
112 | { // The constructor allocates using the given allocator. |
113 | test_allocator_statistics stats; |
114 | std::__allocation_guard<A> guard(A(&stats), size); |
115 | assert(stats.alloc_count == 1); |
116 | assert(guard.__get() != nullptr); |
117 | } |
118 | |
119 | { // The destructor deallocates using the given allocator. |
120 | test_allocator_statistics stats; |
121 | { |
122 | std::__allocation_guard<A> guard(A(&stats), size); |
123 | assert(stats.alloc_count == 1); |
124 | } |
125 | assert(stats.alloc_count == 0); |
126 | } |
127 | |
128 | { // `__release_ptr` prevents deallocation. |
129 | test_allocator_statistics stats; |
130 | A alloc(&stats); |
131 | int* ptr = nullptr; |
132 | { |
133 | std::__allocation_guard<A> guard(alloc, size); |
134 | assert(stats.alloc_count == 1); |
135 | ptr = guard.__release_ptr(); |
136 | } |
137 | assert(stats.alloc_count == 1); |
138 | alloc.deallocate(ptr, size); |
139 | } |
140 | |
141 | { // Using the move constructor doesn't lead to double deletion. |
142 | test_allocator_statistics stats; |
143 | { |
144 | std::__allocation_guard<A> guard1(A(&stats), size); |
145 | assert(stats.alloc_count == 1); |
146 | auto* ptr1 = guard1.__get(); |
147 | |
148 | std::__allocation_guard<A> guard2 = std::move(guard1); |
149 | assert(stats.alloc_count == 1); |
150 | assert(guard1.__get() == nullptr); |
151 | assert(guard2.__get() == ptr1); |
152 | } |
153 | assert(stats.alloc_count == 0); |
154 | } |
155 | |
156 | { // Using the move assignment operator doesn't lead to double deletion. |
157 | using A2 = AssignableAllocator<int>; |
158 | |
159 | test_allocator_statistics stats; |
160 | { |
161 | std::__allocation_guard<A2> guard1(A2(stats), size); |
162 | assert(stats.alloc_count == 1); |
163 | std::__allocation_guard<A2> guard2(A2(stats), size); |
164 | assert(stats.alloc_count == 2); |
165 | auto* ptr1 = guard1.__get(); |
166 | |
167 | guard2 = std::move(guard1); |
168 | assert(stats.alloc_count == 1); |
169 | assert(guard1.__get() == nullptr); |
170 | assert(guard2.__get() == ptr1); |
171 | } |
172 | assert(stats.alloc_count == 0); |
173 | } |
174 | |
175 | { // Self-assignment is a no-op. |
176 | using A2 = AssignableAllocator<int>; |
177 | |
178 | test_allocator_statistics stats; |
179 | { |
180 | std::__allocation_guard<A2> guard(A2(stats), size); |
181 | assert(stats.alloc_count == 1); |
182 | auto* ptr = guard.__get(); |
183 | |
184 | guard = std::move(guard); |
185 | assert(stats.alloc_count == 1); |
186 | assert(guard.__get() == ptr); |
187 | } |
188 | assert(stats.alloc_count == 0); |
189 | } |
190 | |
191 | return 0; |
192 | } |
193 | |