* Document that read() must not be given a view of the document itself basic_json_document::read() releases the previous text, the decoded strings and the edits before it parses its input. An input that points into them (source(), get_string(), number_token(), or a string of an edited document) is read after its memory was cleared or freed. A guard that copies such input costs about 1 ns per read() (5 % of a read of a 7-byte input), which this API exists to avoid, so state the precondition in read.md, lifetime.md and the pages of the views that hand out such strings instead, and show how to copy the string first. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Assert in debug builds that read() is not given its own text read() releases the document's text, decoded strings, owned image and edits before it parses, so an input that points into them is read after release. Without NDEBUG, read() now asserts that a borrowed input, a copied contiguous input and (before C++17) a string_ref input are not held by the document: its owned text, its decoded strings, an owned image, and the current buffer of edited strings. Release builds keep the documented precondition without a check, which would cost 2-4.5 % per read() of a small message. JSON_ASSERT is undefined at the end of json.hpp, so the view uses assert() through a new internal NLOHMANN_VIEW_ASSERT. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
4.7 KiB
Ownership and lifetime
A document either borrows the text it was parsed from, or owns its own copy of it; call
owns_source() to find out which happened.
parse() decides this from the value category and type of its argument (an
lvalue #!cpp std::string is borrowed; an rvalue #!cpp std::string is moved in, owned without a copy; a stream is
read into an owned buffer; and so on — see parse's Notes for the
full table). parse_copy() always owns a copy, regardless of the input.
!!! warning "A borrowed document depends on your buffer"
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
exactly as it would be for a dangling `#!cpp std::string_view`.
When a view is valid
A view is valid only while all of the following hold:
- the document is alive,
- the document has not been re-parsed since the view was taken (with
read()orparse()into it), - the document has not had
shrink_to_fit()called on it since, and - if the document borrows its source text, that text is still alive and unmodified.
Moving the basic_json_document<> itself is fine and does not invalidate its views: the index is a separate heap
allocation that keeps its address across the move. Take a fresh view from
root() whenever any of the other conditions above was not met. Edits of an
editable document keep views valid, too; see What stays valid.
Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.
Why views can dangle
A view can dangle, exactly like a #!cpp std::string_view: nothing stops you from using it after its document is
gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a #!cpp std::shared_ptr in
the document and a #!cpp std::weak_ptr in each view) would make it larger, add reference counting to every copy,
and a lock() to every access — exactly the costs the view exists to avoid. What is caught is the obvious case:
because a view dies with its document, root() is not callable on a
temporary document: #!cpp auto v = json_document::parse(text).root(); does not compile. Give the document a name
first.
The same holds for what a view hands out without copying: a string from
get_string(), a token from
number_token(), and string_view members
of decoded structs are valid only as long as the document and, if it borrows, its source text.
This includes read(): it releases the document's text, decoded strings, and
edits before it parses its input, so the input must not be a string_view obtained from the document itself: its
source(), a string from get_string(), a token from number_token(), or a
key from an iterator (those of a borrowed text that the caller owns, read unchanged, are not affected). To re-parse a
string that the document holds, for example JSON embedded in a string value, copy the string into a #!cpp std::string
first and pass the copy; see read() for the exact list.
??? example "Example: borrowed and owned documents, and when views become invalid"
```cpp
--8<-- "examples/json_view_ownership.cpp"
```
Output:
```json
--8<-- "examples/json_view_ownership.output"
```
See also
- Zero-copy JSON views - overview
basic_json_document- the document that owns the parsed indexowns_source()- whether a document borrows its source text
Version history
- Added in version 3.13.0.