diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index 760972dff..2a670f81c 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -562,7 +562,11 @@ binary32 or binary64 field and have no encoding for `#!cpp long double`. ## `AllocatorType` `AllocatorType` is instantiated with **one** argument, for each of `object_t`, `array_t`, `string_t`, `binary_t`, -`basic_json`, and `#!cpp std::pair`. +`basic_json`, `#!cpp std::pair`, and `#!cpp std::pair`. + +`AllocatorType` is not the only allocator a `basic_json` uses. It allocates the JSON values themselves, but most +temporary storage is allocated with `#!cpp std::allocator`. This includes the parser's stacks and the stacks that +process deeply nested values without recursion. ### Always required diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 1090ed105..c578fd11a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1003,7 +1003,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using copy_worklist_t = std::vector>; /// scratch space to build the key skeleton of an object copy in one go - using copy_scratch_t = std::vector>; + using copy_scratch_value_t = std::pair; + using copy_scratch_t = std::vector>; /// @brief copy everything of @a src into @a dst but its type and value static void copy_metadata(const basic_json& src, basic_json& dst) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 647312562..b92644c40 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -25835,7 +25835,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using copy_worklist_t = std::vector>; /// scratch space to build the key skeleton of an object copy in one go - using copy_scratch_t = std::vector>; + using copy_scratch_value_t = std::pair; + using copy_scratch_t = std::vector>; /// @brief copy everything of @a src into @a dst but its type and value static void copy_metadata(const basic_json& src, basic_json& dst) diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 5c7b4230f..cdc532e3a 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -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, so only the scratch space of the +// iterative deep copy allocates std::pair +std::size_t scratch_pair_allocations = 0; + +template +struct is_scratch_pair : std::false_type {}; + +template +struct is_scratch_pair> : std::integral_constant < bool, !std::is_const::value > {}; + +template +struct scratch_counting_allocator : std::allocator +{ + using std::allocator::allocator; + + T* allocate(std::size_t n) + { + if (is_scratch_pair::value) + { + ++scratch_pair_allocations; + } + return std::allocator::allocate(n); + } + +#ifdef __cpp_lib_allocate_at_least + // std::allocator::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::value) + { + ++scratch_pair_allocations; + } + return std::allocator::allocate_at_least(n); + } +#endif + + template + struct rebind + { + using other = scratch_counting_allocator; + }; +}; +} // namespace + +TEST_CASE("deep copy uses the provided allocator") +{ + using counting_json = nlohmann::basic_json; + + // 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