mirror of
https://github.com/nlohmann/json.git
synced 2026-09-28 04:55:50 +00:00
Allocate the deep copy's key scratch space with the provided allocator (#5573)
* Allocate the deep copy's key scratch space with the provided allocator The iterative deep copy builds each object's keys in a temporary vector of key/value pairs before handing them to the object's range constructor. That vector holds basic_json values, so like the values themselves it now uses AllocatorType instead of std::allocator. Also document that AllocatorType covers the JSON values, while most temporary storage still uses std::allocator. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Count allocate_at_least in the scratch-counting test allocator From C++23 on, libc++'s containers allocate through allocate_at_least when the allocator has one. The test allocator inherited it from std::allocator, so the scratch allocations were not counted and the test failed on Xcode. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -270,6 +270,82 @@ TEST_CASE("controlled bad_alloc")
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// counts the allocations of pairs with a non-const first member: the object
|
||||
// types store std::pair<const Key, T>, so only the scratch space of the
|
||||
// iterative deep copy allocates std::pair<Key, T>
|
||||
std::size_t scratch_pair_allocations = 0;
|
||||
|
||||
template<class T>
|
||||
struct is_scratch_pair : std::false_type {};
|
||||
|
||||
template<class K, class V>
|
||||
struct is_scratch_pair<std::pair<K, V>> : std::integral_constant < bool, !std::is_const<K>::value > {};
|
||||
|
||||
template<class T>
|
||||
struct scratch_counting_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
if (is_scratch_pair<T>::value)
|
||||
{
|
||||
++scratch_pair_allocations;
|
||||
}
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_allocate_at_least
|
||||
// std::allocator<T>::allocate_at_least would bypass the counting, and
|
||||
// libc++'s containers prefer it over allocate from C++23 on
|
||||
auto allocate_at_least(std::size_t n)
|
||||
{
|
||||
if (is_scratch_pair<T>::value)
|
||||
{
|
||||
++scratch_pair_allocations;
|
||||
}
|
||||
return std::allocator<T>::allocate_at_least(n);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = scratch_counting_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("deep copy uses the provided allocator")
|
||||
{
|
||||
using counting_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
scratch_counting_allocator>;
|
||||
|
||||
// deeper than the 128 levels the copy constructor descends into, so the
|
||||
// innermost objects are copied by the iterative deep copy
|
||||
counting_json j = 1;
|
||||
for (std::size_t i = 0; i < 300; ++i)
|
||||
{
|
||||
counting_json wrapper = counting_json::object();
|
||||
wrapper["a"] = std::move(j);
|
||||
j = std::move(wrapper);
|
||||
}
|
||||
|
||||
scratch_pair_allocations = 0;
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
const counting_json copy(j);
|
||||
CHECK(scratch_pair_allocations > 0);
|
||||
CHECK(copy == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
|
||||
Reference in New Issue
Block a user