Compare commits

...
Author SHA1 Message Date
Niels LohmannandClaude c401d495a3 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>
2026-09-01 05:29:01 +00:00
Claude 2998f2c731 Update the serializer test for the non-owning adapter ctor
check_escaped constructed the serializer with output_adapter<char>(ss),
which produced the old owning output_adapter_t. The ctor now takes a
non-owning output_adapter_protocol<char>*, so build the concrete
output_stream_adapter on the stack and pass its address, matching how
dump() and operator<< now call it.

Signed-off-by: Claude <noreply@anthropic.com>
2026-09-01 05:26:52 +00:00
Claude 1232fa947c 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>
2026-09-01 05:26:52 +00:00
4 changed files with 22 additions and 15 deletions
@@ -63,13 +63,14 @@ class serializer
public: 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] ichar indentation character to use
@param[in] error_handler_ how to react on decoding errors @param[in] error_handler_ how to react on decoding errors
*/ */
serializer(output_adapter_t<char> s, const char ichar, serializer(output_adapter_protocol<char>& s, const char ichar,
error_handler_t error_handler_ = error_handler_t::strict) error_handler_t error_handler_ = error_handler_t::strict)
: o(std::move(s)) : o(&s)
, loc(std::localeconv()) , loc(std::localeconv())
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep))) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
@@ -1677,8 +1678,8 @@ class serializer
} }
private: private:
/// the output of the serializer /// the output of the serializer (non-owning; the adapter lives at the call site)
output_adapter_t<char> o = nullptr; output_adapter_protocol<char>* o = nullptr;
/// a (hopefully) large enough character buffer /// a (hopefully) large enough character buffer
std::array<char, 64> number_buffer{{}}; std::array<char, 64> number_buffer{{}};
+4 -2
View File
@@ -1341,7 +1341,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const error_handler_t error_handler = error_handler_t::strict) const const error_handler_t error_handler = error_handler_t::strict) const
{ {
string_t result; string_t result;
serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler); detail::output_string_adapter<char, string_t> string_adapter(result);
serializer s(string_adapter, indent_char, error_handler);
if (indent >= 0) if (indent >= 0)
{ {
@@ -4055,7 +4056,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
o.width(0); o.width(0);
// do the actual serialization // 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());
s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));
return o; return o;
} }
+10 -7
View File
@@ -21043,13 +21043,14 @@ class serializer
public: 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] ichar indentation character to use
@param[in] error_handler_ how to react on decoding errors @param[in] error_handler_ how to react on decoding errors
*/ */
serializer(output_adapter_t<char> s, const char ichar, serializer(output_adapter_protocol<char>& s, const char ichar,
error_handler_t error_handler_ = error_handler_t::strict) error_handler_t error_handler_ = error_handler_t::strict)
: o(std::move(s)) : o(&s)
, loc(std::localeconv()) , loc(std::localeconv())
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep))) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
@@ -22657,8 +22658,8 @@ class serializer
} }
private: private:
/// the output of the serializer /// the output of the serializer (non-owning; the adapter lives at the call site)
output_adapter_t<char> o = nullptr; output_adapter_protocol<char>* o = nullptr;
/// a (hopefully) large enough character buffer /// a (hopefully) large enough character buffer
std::array<char, 64> number_buffer{{}}; std::array<char, 64> number_buffer{{}};
@@ -24360,7 +24361,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const error_handler_t error_handler = error_handler_t::strict) const const error_handler_t error_handler = error_handler_t::strict) const
{ {
string_t result; string_t result;
serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler); detail::output_string_adapter<char, string_t> string_adapter(result);
serializer s(string_adapter, indent_char, error_handler);
if (indent >= 0) if (indent >= 0)
{ {
@@ -27074,7 +27076,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
o.width(0); o.width(0);
// do the actual serialization // 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());
s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));
return o; return o;
} }
+2 -1
View File
@@ -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) void check_escaped(const char* original, const char* escaped, const bool ensure_ascii)
{ {
std::stringstream ss; std::stringstream ss;
json::serializer s(nlohmann::detail::output_adapter<char>(ss), ' '); nlohmann::detail::output_stream_adapter<char> adapter(ss);
json::serializer s(adapter, ' ');
s.dump_escaped(original, ensure_ascii); s.dump_escaped(original, ensure_ascii);
s.flush(); // dump_escaped writes into the serializer's internal buffer s.flush(); // dump_escaped writes into the serializer's internal buffer
CHECK(ss.str() == escaped); CHECK(ss.str() == escaped);