//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// <memory>
// To allow checking that self-move works correctly.
// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move
// template<class _Alloc>
// struct __allocation_guard;
#include <__memory/allocation_guard.h>
#include <cassert>
#include <type_traits>
#include <utility>
#include "test_allocator.h"
using A = test_allocator<int>;
// A trimmed-down version of `test_allocator` that is copy-assignable (in general allocators don't have to support copy
// assignment).
template <class T>
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<value_type>::type;
using const_reference = typename std::add_lvalue_reference<const value_type>::type;
template <class U>
struct rebind {
using other = test_allocator<U>;
};
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<value_type>().allocate(n);
}
TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) {
if (stats_ != nullptr) {
--stats_->alloc_count;
}
std::allocator<value_type>().deallocate(p, s);
}
TEST_CONSTEXPR size_type max_size() const TEST_NOEXCEPT { return UINT_MAX / sizeof(T); }
template <class U>
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<U>(val));
#else
::new (static_cast<void*>(p)) T(std::forward<U>(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<std::__allocation_guard<A> >::value, "");
static_assert(std::is_move_constructible<std::__allocation_guard<A> >::value, "");
static_assert(!std::is_copy_assignable<std::__allocation_guard<A> >::value, "");
static_assert(std::is_move_assignable<std::__allocation_guard<A> >::value, "");
int main(int, char**) {
const int size = 42;
{ // The constructor allocates using the given allocator.
test_allocator_statistics stats;
std::__allocation_guard<A> 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<A> 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<A> 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<A> guard1(A(&stats), size);
assert(stats.alloc_count == 1);
auto* ptr1 = guard1.__get();
std::__allocation_guard<A> 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<int>;
test_allocator_statistics stats;
{
std::__allocation_guard<A2> guard1(A2(stats), size);
assert(stats.alloc_count == 1);
std::__allocation_guard<A2> 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<int>;
test_allocator_statistics stats;
{
std::__allocation_guard<A2> 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;
}