//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // // To allow checking that self-move works correctly. // ADDITIONAL_COMPILE_FLAGS: -Wno-self-move // template // struct __allocation_guard; #include <__cxx03/__memory/allocation_guard.h> #include #include #include #include "test_allocator.h" using A = test_allocator; // A trimmed-down version of `test_allocator` that is copy-assignable (in general allocators don't have to support copy // assignment). template struct AssignableAllocator { using size_type = unsigned; using difference_type = int; using value_type = T; using pointer = value_type*; using const_pointer = const value_type*; using reference = typename std::add_lvalue_reference::type; using const_reference = typename std::add_lvalue_reference::type; template struct rebind { using other = test_allocator; }; test_allocator_statistics* stats_ = nullptr; explicit AssignableAllocator(test_allocator_statistics& stats) : stats_(&stats) { ++stats_->count; } TEST_CONSTEXPR_CXX14 AssignableAllocator(const AssignableAllocator& rhs) TEST_NOEXCEPT : stats_(rhs.stats_) { if (stats_ != nullptr) { ++stats_->count; ++stats_->copied; } } TEST_CONSTEXPR_CXX14 AssignableAllocator& operator=(const AssignableAllocator& rhs) TEST_NOEXCEPT { stats_ = rhs.stats_; if (stats_ != nullptr) { ++stats_->count; ++stats_->copied; } return *this; } TEST_CONSTEXPR_CXX14 pointer allocate(size_type n, const void* = nullptr) { if (stats_ != nullptr) { ++stats_->alloc_count; } return std::allocator().allocate(n); } TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) { if (stats_ != nullptr) { --stats_->alloc_count; } std::allocator().deallocate(p, s); } TEST_CONSTEXPR size_type max_size() const TEST_NOEXCEPT { return UINT_MAX / sizeof(T); } template TEST_CONSTEXPR_CXX20 void construct(pointer p, U&& val) { if (stats_ != nullptr) ++stats_->construct_count; #if TEST_STD_VER > 17 std::construct_at(std::to_address(p), std::forward(val)); #else ::new (static_cast(p)) T(std::forward(val)); #endif } TEST_CONSTEXPR_CXX14 void destroy(pointer p) { if (stats_ != nullptr) { ++stats_->destroy_count; } p->~T(); } }; // Move-only. static_assert(!std::is_copy_constructible >::value, ""); static_assert(std::is_move_constructible >::value, ""); static_assert(!std::is_copy_assignable >::value, ""); static_assert(std::is_move_assignable >::value, ""); int main(int, char**) { const int size = 42; { // The constructor allocates using the given allocator. test_allocator_statistics stats; std::__allocation_guard guard(A(&stats), size); assert(stats.alloc_count == 1); assert(guard.__get() != nullptr); } { // The destructor deallocates using the given allocator. test_allocator_statistics stats; { std::__allocation_guard guard(A(&stats), size); assert(stats.alloc_count == 1); } assert(stats.alloc_count == 0); } { // `__release_ptr` prevents deallocation. test_allocator_statistics stats; A alloc(&stats); int* ptr = nullptr; { std::__allocation_guard guard(alloc, size); assert(stats.alloc_count == 1); ptr = guard.__release_ptr(); } assert(stats.alloc_count == 1); alloc.deallocate(ptr, size); } { // Using the move constructor doesn't lead to double deletion. test_allocator_statistics stats; { std::__allocation_guard guard1(A(&stats), size); assert(stats.alloc_count == 1); auto* ptr1 = guard1.__get(); std::__allocation_guard guard2 = std::move(guard1); assert(stats.alloc_count == 1); assert(guard1.__get() == nullptr); assert(guard2.__get() == ptr1); } assert(stats.alloc_count == 0); } { // Using the move assignment operator doesn't lead to double deletion. using A2 = AssignableAllocator; test_allocator_statistics stats; { std::__allocation_guard guard1(A2(stats), size); assert(stats.alloc_count == 1); std::__allocation_guard guard2(A2(stats), size); assert(stats.alloc_count == 2); auto* ptr1 = guard1.__get(); guard2 = std::move(guard1); assert(stats.alloc_count == 1); assert(guard1.__get() == nullptr); assert(guard2.__get() == ptr1); } assert(stats.alloc_count == 0); } { // Self-assignment is a no-op. using A2 = AssignableAllocator; test_allocator_statistics stats; { std::__allocation_guard guard(A2(stats), size); assert(stats.alloc_count == 1); auto* ptr = guard.__get(); guard = std::move(guard); assert(stats.alloc_count == 1); assert(guard.__get() == ptr); } assert(stats.alloc_count == 0); } return 0; }