Stop dump() from heap-allocating its output adapter per call (#5449)

* Stop dump() from heap-allocating its output adapter per call

The serializer held its output sink as output_adapter_t<char>
(a std::shared_ptr<output_adapter_protocol<char>>), 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<char>* 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1oJ2ggRHS37zeVe94QTA1
Signed-off-by: Claude <noreply@anthropic.com>

* Take the output adapter by reference at the serializer ctor

Per review: the serializer still holds the adapter as a non-owning
pointer, but the constructor now takes output_adapter_protocol<char>&
and takes its address internally, so every call site passes a
reference. A reference cannot be null and reads as a borrow, which
makes the lifetime contract harder to get wrong than handing over a
raw pointer. The stored member and the write path are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAYM1qhSA2FDaDcGfPW3fG
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Niels Lohmann
2026-09-11 08:22:50 +02:00
committed by GitHub
co-authored by Claude
parent ff80ed3295
commit da42a627dc
4 changed files with 24 additions and 17 deletions
+11 -8
View File
@@ -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<char> s, const char ichar,
serializer(output_adapter_protocol<char>& 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<char> o = nullptr;
/// the output of the serializer (non-owning; the adapter lives at the call site)
output_adapter_protocol<char>* o = nullptr;
/// a (hopefully) large enough character buffer
std::array<char, 64> 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<char, string_t> string_adapter(result);
if (indent >= 0)
{
serializer s(detail::output_adapter<char, string_t>(result), indent_char,
serializer s(string_adapter, indent_char,
true, ensure_ascii, static_cast<std::size_t>(indent), error_handler);
s.dump(*this);
}
else
{
serializer s(detail::output_adapter<char, string_t>(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<char>(o), o.fill(),
detail::output_stream_adapter<char> stream_adapter(o);
serializer s(stream_adapter, o.fill(),
pretty_print, false, static_cast<std::size_t>(indentation));
s.dump(j);
return o;