From c5e2a95ff195df058e3f8f808947b92f0116e650 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 20:08:10 +0000 Subject: [PATCH] Stop dump() from heap-allocating its output adapter per call The serializer held its output sink as output_adapter_t (a std::shared_ptr>), which dump() and operator<< built via make_shared -- one heap allocation per call for a sink that only wraps a reference to the caller's string or stream. Hold the sink as a non-owning output_adapter_protocol* instead and construct the concrete adapter on the stack at the call site. The write path (o->write_characters) is unchanged, so output is byte-for-byte identical; a compact dump() of a small object drops from 2 heap allocations to 1 (only the returned string remains), ~3% faster. Completes the per-call allocation cleanup on this branch, which already removed the indent_string buffer (both were reported in #5413). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01L1oJ2ggRHS37zeVe94QTA1 Signed-off-by: Claude --- include/nlohmann/detail/output/serializer.hpp | 11 ++++++----- include/nlohmann/json.hpp | 8 +++++--- single_include/nlohmann/json.hpp | 19 +++++++++++-------- tests/src/unit-convenience.cpp | 3 ++- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 3dd9162df..3ea359109 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -62,7 +62,8 @@ class serializer public: /*! - @param[in] s output stream to serialize to + @param[in] s output adapter to serialize to; not owned by the serializer, + so it must outlive it (it lives at the call site) @param[in] ichar indentation character to use @param[in] pretty_print_ whether the output shall be pretty-printed @param[in] ensure_ascii_ If @a ensure_ascii_ is true, all non-ASCII @@ -76,12 +77,12 @@ class serializer being threaded through every call to @ref dump, @ref dump_internal and @ref dump_iteratively. */ - serializer(output_adapter_t s, const char ichar, + serializer(output_adapter_protocol* s, const char ichar, const bool pretty_print_ = false, const bool ensure_ascii_ = false, const std::size_t indent_step_ = 0, error_handler_t error_handler_ = error_handler_t::strict) - : o(std::move(s)) + : o(s) , locale(std::localeconv()) , indent_char(ichar) , pretty_print(pretty_print_) @@ -1678,8 +1679,8 @@ class serializer const char decimal_point; }; - /// the output of the serializer - output_adapter_t o = nullptr; + /// the output of the serializer (non-owning; the adapter lives at the call site) + output_adapter_protocol* o = nullptr; /// a (hopefully) large enough character buffer std::array number_buffer{{}}; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 7243c3c44..48cf3c4b0 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1343,16 +1343,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const error_handler_t error_handler = error_handler_t::strict) const { string_t result; + detail::output_string_adapter string_adapter(result); if (indent >= 0) { - serializer s(detail::output_adapter(result), indent_char, + serializer s(&string_adapter, indent_char, true, ensure_ascii, static_cast(indent), error_handler); s.dump(*this); } else { - serializer s(detail::output_adapter(result), indent_char, + serializer s(&string_adapter, indent_char, false, ensure_ascii, 0, error_handler); s.dump(*this); } @@ -4083,7 +4084,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec o.width(0); // do the actual serialization - serializer s(detail::output_adapter(o), o.fill(), + detail::output_stream_adapter stream_adapter(o); + serializer s(&stream_adapter, o.fill(), pretty_print, false, static_cast(indentation)); s.dump(j); return o; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8b1f4506e..b3fcd6cdd 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -21410,7 +21410,8 @@ class serializer public: /*! - @param[in] s output stream to serialize to + @param[in] s output adapter to serialize to; not owned by the serializer, + so it must outlive it (it lives at the call site) @param[in] ichar indentation character to use @param[in] pretty_print_ whether the output shall be pretty-printed @param[in] ensure_ascii_ If @a ensure_ascii_ is true, all non-ASCII @@ -21424,12 +21425,12 @@ class serializer being threaded through every call to @ref dump, @ref dump_internal and @ref dump_iteratively. */ - serializer(output_adapter_t s, const char ichar, + serializer(output_adapter_protocol* s, const char ichar, const bool pretty_print_ = false, const bool ensure_ascii_ = false, const std::size_t indent_step_ = 0, error_handler_t error_handler_ = error_handler_t::strict) - : o(std::move(s)) + : o(s) , locale(std::localeconv()) , indent_char(ichar) , pretty_print(pretty_print_) @@ -23026,8 +23027,8 @@ class serializer const char decimal_point; }; - /// the output of the serializer - output_adapter_t o = nullptr; + /// the output of the serializer (non-owning; the adapter lives at the call site) + output_adapter_protocol* o = nullptr; /// a (hopefully) large enough character buffer std::array number_buffer{{}}; @@ -24738,16 +24739,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const error_handler_t error_handler = error_handler_t::strict) const { string_t result; + detail::output_string_adapter string_adapter(result); if (indent >= 0) { - serializer s(detail::output_adapter(result), indent_char, + serializer s(&string_adapter, indent_char, true, ensure_ascii, static_cast(indent), error_handler); s.dump(*this); } else { - serializer s(detail::output_adapter(result), indent_char, + serializer s(&string_adapter, indent_char, false, ensure_ascii, 0, error_handler); s.dump(*this); } @@ -27478,7 +27480,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec o.width(0); // do the actual serialization - serializer s(detail::output_adapter(o), o.fill(), + detail::output_stream_adapter stream_adapter(o); + serializer s(&stream_adapter, o.fill(), pretty_print, false, static_cast(indentation)); s.dump(j); return o; diff --git a/tests/src/unit-convenience.cpp b/tests/src/unit-convenience.cpp index 266497867..fcf6ba73c 100644 --- a/tests/src/unit-convenience.cpp +++ b/tests/src/unit-convenience.cpp @@ -98,7 +98,8 @@ void check_escaped(const char* original, const char* escaped = "", bool ensure_a void check_escaped(const char* original, const char* escaped, const bool ensure_ascii) { std::stringstream ss; - json::serializer s(nlohmann::detail::output_adapter(ss), ' ', false, ensure_ascii); + nlohmann::detail::output_stream_adapter adapter(ss); + json::serializer s(&adapter, ' ', false, ensure_ascii); s.dump_escaped(original); s.flush(); // dump_escaped writes into the serializer's internal buffer CHECK(ss.str() == escaped);