|
pjson 1.0.0
A small, owning JSON value for C++11
|
Most applications should use pjson's built-in allocator. If an embedded system, arena, pool, or allocation tracer needs control over persistent DOM storage, implement pjson::Allocator and bind values to it. Follow along with examples/src/09_custom_allocator.cpp.
Without an allocator argument, a value uses pjson's built-in allocator:
The direct value is owned by its C++ scope. The parse result uses the same provenance-aware pjson::unique_ptr returned by every DOM parse overload, so it is also released automatically. Children returned by find() or findPointer() are borrowed views into the owning tree—never delete them yourself.
Every pjson value is allocator-bound, including default-constructed values. A custom allocator changes where selected persistent DOM allocations come from; it does not change the tree's ownership model.
Derive from the runtime interface and implement both operations:
The contract is:
allocate returns non-null storage satisfying the requested byte size and alignment, or throws (normally std::bad_alloc). Returning nullptr is not a supported failure signal.deallocate receives the original pointer and matching size, alignment, and kind. It must not throw.AllocationKind lets a pool maintain separate free lists or statistics:
| Kind | Persistent allocation represented |
|---|---|
NodeAllocation | A dynamically owned pjson node, including a parsed root |
StringAllocation | The std::string wrapper for a string-valued node |
ArrayAllocation | The internal wrapper for an array-valued node |
ObjectAllocation | The internal wrapper for an object-valued node |
The hook deliberately does not replace every allocation in the process. The internal buffers/nodes allocated by std::string, std::vector, and std::map, and transient parsing, serialization, pointer, patch, and validation workspaces, continue to use the standard allocator.
A directly constructed root remains owned by the place where it was created:
Parsing must allocate the root dynamically, so every overload returns pjson::unique_ptr:
pjson::unique_ptr is std::unique_ptr<pjson, pjson::ValueDeleter>. Its stateless deleter reads allocator provenance from the root and returns the root through the correct allocator. Do not replace that deleter or call delete on the root. Moving the smart pointer transfers the root but does not own or extend the allocator's lifetime.
Allocator-aware overloads exist for std::string, (const char*, size_t), and std::istream, with optional ParseError and ParseOptions. parseStream() uses standard allocation for its temporary input buffer but uses the supplied allocator for the persistent DOM. SAX parsing builds no persistent DOM and has no allocator overload.
Allocator provenance is part of a value's lifetime contract:
| Operation | Allocator behavior |
|---|---|
pjson copy(source) | Deep copy using source's allocator |
pjson copy(source, destinationAllocator) | Deep copy into the named allocator |
destination = source / copyFrom(source) | Deep copy while preserving the destination allocator |
pjson moved(std::move(source)) | O(1) transfer with the source allocator; source becomes null |
pjson moved(std::move(source), destinationAllocator) | O(1) when allocators match; otherwise deep-transfer, then source becomes null |
destination = std::move(source) | Preserves the destination allocator; same-allocator storage transfer may still destroy the old destination tree, while cross-allocator transfer may allocate |
left.swap(right) | O(1) only when left.canSwap(right); otherwise a safe no-op |
Check compatibility whenever two values may have come from different allocator domains:
Do not infer allocator ownership from equality or value type. Use &value.getAllocator() when provenance matters. A successfully moved-from source is JSON null but remains bound to its original allocator.
Allocator-aware in-memory parsing catches failures during DOM construction, destroys partial trees, returns an empty pjson::unique_ptr, and fills ParseError when supplied. parseStream() first fills a standard-allocated input buffer, so an exception-enabled stream or failure in that buffer can still throw before DOM construction.
Other operations that allocate—such as string/container mutation, deep copy, and cross-allocator move—may propagate std::bad_alloc. Copy assignment, cross-allocator move assignment, resetTo, string replacement, missing-key insertion, and array growth preserve existing data in their documented/tested failure paths. JSON Patch and Merge Patch are noexcept; allocation or internal failures are reported through false and PatchError, and the target is left unchanged.
An allocator should remain usable while failed operations unwind, because pjson may need it to release partially constructed nodes.
The companion example implements a small counting allocator using global operator new/delete. It is intentionally an instrumentation example, not an arena implementation:
pjson::unique_ptr.Allocator is borrowed and must outlive the entire bound tree.pjson::unique_ptr and ValueDeleter.canSwap() distinguishes the O(1) same-allocator path from a cross-allocator no-op.Return to the tutorial index, or consult the browsable API reference.