llvm-project/libcxx/test/support/increasing_allocator.h
Peng Liu 31824b2a11
[libc++] Fix shrink_to_fit to swap buffer only when capacity is strictly smaller (#127321)
The current implementation of the `shrink_to_fit()` function of
`basic_string` swaps to the newly allocated buffer when the new buffer
has the same capacity as the existing one. While this is not incorrect,
it is truly unnecessary to swap to an equally-sized buffer. With equal
capacity, we should keep using the existing buffer and simply deallocate
the new one, avoiding the extra work of copying elements.

The desired behavior was documented in the following comment within the
function:


61ad08792a/libcxx/include/string (L3560-L3566)

However, the existing implementation did not exactly conform to this
guideline, which is a QoI matter.

This PR modifies the `shrink_to_fit()` function to ensure that the
buffer is only swapped when the new allocation is strictly smaller than
the existing one. When the capacities are equal, the new buffer will be
discarded without copying the elements. This is achieved by including
the `==` check in the above conditional logic.
2025-02-22 14:50:48 +01:00

125 lines
4.0 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef TEST_SUPPORT_INCREASING_ALLOCATOR_H
#define TEST_SUPPORT_INCREASING_ALLOCATOR_H
#include <cstddef>
#include <limits>
#include <memory>
#include "test_macros.h"
// The increasing_allocator is a custom allocator that enforces an increasing minimum allocation size,
// ensuring that it allocates an increasing amount of memory, possibly exceeding the requested amount.
// This unique behavior is particularly useful for testing the shrink_to_fit functionality in std::vector,
// vector<bool>, and std::basic_string, ensuring that shrink_to_fit does not increase the capacity of
// the allocated memory.
template <typename T>
struct increasing_allocator {
using value_type = T;
std::size_t min_elements = 1000;
increasing_allocator() = default;
template <typename U>
TEST_CONSTEXPR_CXX20 increasing_allocator(const increasing_allocator<U>& other) TEST_NOEXCEPT
: min_elements(other.min_elements) {}
#if TEST_STD_VER >= 23
TEST_CONSTEXPR_CXX23 std::allocation_result<T*> allocate_at_least(std::size_t n) {
if (n < min_elements)
n = min_elements;
min_elements += 1000;
return std::allocator<T>{}.allocate_at_least(n);
}
#endif // TEST_STD_VER >= 23
TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) TEST_NOEXCEPT { std::allocator<T>().deallocate(p, n); }
};
template <typename T, typename U>
TEST_CONSTEXPR_CXX20 bool operator==(increasing_allocator<T>, increasing_allocator<U>) TEST_NOEXCEPT {
return true;
}
template <std::size_t MinAllocSize, typename T>
class min_size_allocator {
public:
using value_type = T;
min_size_allocator() = default;
template <typename U>
TEST_CONSTEXPR_CXX20 min_size_allocator(const min_size_allocator<MinAllocSize, U>&) TEST_NOEXCEPT {}
TEST_NODISCARD TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) {
if (n < MinAllocSize)
n = MinAllocSize;
return static_cast<T*>(::operator new(n * sizeof(T)));
}
TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t) TEST_NOEXCEPT { ::operator delete(static_cast<void*>(p)); }
template <typename U>
struct rebind {
using other = min_size_allocator<MinAllocSize, U>;
};
};
template <std::size_t MinAllocSize, typename T, typename U>
TEST_CONSTEXPR_CXX20 bool
operator==(const min_size_allocator<MinAllocSize, T>&, const min_size_allocator<MinAllocSize, U>&) {
return true;
}
template <std::size_t MinAllocSize, typename T, typename U>
TEST_CONSTEXPR_CXX20 bool
operator!=(const min_size_allocator<MinAllocSize, T>&, const min_size_allocator<MinAllocSize, U>&) {
return false;
}
template <typename T>
class pow2_allocator {
public:
using value_type = T;
pow2_allocator() = default;
template <typename U>
TEST_CONSTEXPR_CXX20 pow2_allocator(const pow2_allocator<U>&) TEST_NOEXCEPT {}
TEST_NODISCARD TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) {
return static_cast<T*>(::operator new(next_power_of_two(n) * sizeof(T)));
}
TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t) TEST_NOEXCEPT { ::operator delete(static_cast<void*>(p)); }
private:
TEST_CONSTEXPR_CXX20 std::size_t next_power_of_two(std::size_t n) const {
if ((n & (n - 1)) == 0)
return n;
for (std::size_t shift = 1; shift < std::numeric_limits<std::size_t>::digits; shift <<= 1) {
n |= n >> shift;
}
return n + 1;
}
};
template <typename T, typename U>
TEST_CONSTEXPR_CXX20 bool operator==(const pow2_allocator<T>&, const pow2_allocator<U>&) {
return true;
}
template <typename T, typename U>
TEST_CONSTEXPR_CXX20 bool operator!=(const pow2_allocator<T>&, const pow2_allocator<U>&) {
return false;
}
#endif // TEST_SUPPORT_INCREASING_ALLOCATOR_H