Managing StringEditor Storage
For most edits, StringEditor can manage its own storage and you never need to
think about capacity.
Storage becomes interesting when you already know how large the result will be, reuse one editor many times, or discover
that a small visible value retains more memory than expected.
This page explains those deliberate exceptions. It connects capacity to the native unit of each encoding, shows when copy-on-write performs a real copy, and helps you decide whether reserving, detaching, clearing, or compacting will actually improve the workflow.
Measure Capacity in the Encoding’s Native Unit
capacity() and reserve() use the editor’s native storage unit:
StringEditorandU8StringEditoruse bytes.U16StringEditoruses UTF-16 data units, not bytes and not decoded code points.U32StringEditoruses code points, which are also its native data units.
Reserve the final native amount once when it can be calculated cheaply. Reserving before every append can repeatedly materialize exact-sized storage and removes the benefit of amortized growth.
/// Reserve string storage once when the final native size is already known.
///
/// Reserving before every append step can repeatedly materialize new storage.
/// The better pattern is to calculate the final native size, reserve once, and
/// then append the fragments. Unreserved growth may otherwise reallocate and
/// copy the existing text several times.
void reserveForAppend() {
const auto fragments = std::array{
"Aurora station: céu limpo"_el,
"Wind tunnel: brise légère"_el,
"Observatory: 星が明るい"_el,
};
auto requiredBytes = std::size_t{0};
for (const auto &fragment : fragments) {
requiredBytes += fragment.length().toSizeT() + 1U;
}
auto planned = el::StringEditor{};
planned.reserve(el::ByteLength::fromSizeT(requiredBytes));
el::io::printLine("Planned reservation"_el);
el::io::printLine(" required bytes : "_el, requiredBytes);
el::io::printLine(" length after reserve : "_el, planned.length());
el::io::printLine(" capacity after reserve : "_el, planned.capacity());
for (const auto &fragment : fragments) {
planned.append(fragment).append(U'\n');
}
el::io::printLine(" length after append : "_el, planned.length());
el::io::printLine(" capacity after append : "_el, planned.capacity());
auto repeated = el::StringEditor{};
el::io::printLine();
el::io::printLine("Repeated exact reservations"_el);
for (const auto &fragment : fragments) {
const auto nextLength = repeated.length().toSizeT() + fragment.length().toSizeT() + 1U;
repeated.reserve(el::ByteLength::fromSizeT(nextLength));
repeated.append(fragment).append(U'\n');
el::io::printLine(" after step: length "_el, repeated.length(), ", capacity "_el, repeated.capacity());
}
el::io::printLine();
el::io::printLine("Final report:"_el);
el::io::print(planned);
}
Planned reservation
required bytes : 84
length after reserve : 0
capacity after reserve : 84
length after append : 84
capacity after append : 84
Repeated exact reservations
after step: length 27, capacity 27
after step: length 55, capacity 55
after step: length 84, capacity 84
Final report:
Aurora station: céu limpo
Wind tunnel: brise légère
Observatory: 星が明るい
Pay for Detachment at a Deliberate Point
Mutating operations detach automatically.
Call detach() only when low-level code deliberately wants to pay the copy cost before a group of edits or when
diagnostics must make the transition from shared to unique storage explicit.
/// Manual detaching makes an automatic copy-on-write step explicit.
///
/// A write operation detaches shared string storage automatically. Calling
/// `detach()` directly is therefore rare. It is mainly useful when low-level
/// code wants to make exclusive storage visible before a group of edits, or
/// when diagnostics need to show exactly where sharing ends.
void manualDetach() {
auto fieldNote = el::StringEditor{"Fjord station: lichen sample 17, lumière froide"_el};
auto archiveCopy = fieldNote;
const auto yesNo = el::BooleanFormat::yesNo();
el::io::printLine("Before detach:"_el);
el::io::printLine(" same visible storage : "_el, yesNo, fieldNote.storageId() == archiveCopy.storageId());
fieldNote.detach();
el::io::printLine("After detach:"_el);
el::io::printLine(" same visible storage : "_el, yesNo, fieldNote.storageId() == archiveCopy.storageId());
fieldNote.replaceAll("sample 17"_el, "sample 17A"_el);
fieldNote.append(" | checked"_el);
el::io::printLine();
el::io::printLine("Edited note : "_el, fieldNote);
el::io::printLine("Archive copy: "_el, archiveCopy);
constexpr auto debugDetails =
el::DebugViewDetail::BackingStore | el::DebugViewDetail::Size | el::DebugViewDetail::Range;
el::io::printLine();
el::io::printLine("Debug view after editing:"_el);
el::io::printLine(el::toDebugString(fieldNote, debugDetails));
el::io::printLine(el::toDebugString(archiveCopy, debugDetails));
}
Before detach:
same visible storage : yes
After detach:
same visible storage : no
Edited note : Fjord station: lichen sample 17A, lumière froide | checked
Archive copy: Fjord station: lichen sample 17, lumière froide
Debug view after editing:
U8StringEditor:
length: 59
characterLength: 58
backingStorageId: 0x262357030cfc395e:0x1a0ad471f0125663
backingLength: 60
selectedRange: index: 0 - 59 (length: 59)
U8StringEditor:
length: 48
characterLength: 47
backingStorageId: 0x262357030cfddb0e:0x1a0ad471f013b45e
backingLength: 49
selectedRange: index: 0 - 48 (length: 48)
Keep Capacity for Reuse or Give It Back
clear() keeps capacity for the next editing pass.
reset() releases the editor state.
shrinkToFit() compacts a completed editor when retained spare capacity is materially larger than the value that must
remain alive.
Compaction allocates and copies, so it is not routine cleanup. Use it at a storage boundary after measuring or observing meaningful retention, not after every edit.
/// `shrinkToFit()` is a deliberate compaction step, not routine cleanup.
///
/// A small construction can deliberately reserve its final maximum size.
/// Calling `shrinkToFit()` after construction releases unused capacity before
/// the editor is converted to a stored read-only string.
void shrinkSlices() {
auto note = el::StringEditor{};
note.reserve(el::ByteLength{96U});
note.append("Specimen: "_el);
note.append("Luzula sylvatica"_el);
el::io::printLine("Before compaction:"_el);
el::io::printLine(" text ........: "_el, note);
el::io::printLine(" length ......: "_el, note.length());
el::io::printLine(" capacity ....: "_el, note.capacity());
el::io::printLine(" memory usage : "_el, note.memoryUsage());
note.shrinkToFit();
el::io::printLine();
el::io::printLine("After shrinkToFit:"_el);
el::io::printLine(" text ........: "_el, note);
el::io::printLine(" length ......: "_el, note.length());
el::io::printLine(" capacity ....: "_el, note.capacity());
el::io::printLine(" memory usage : "_el, note.memoryUsage());
const auto storedNote = el::String{note};
el::io::printLine("Stored result .: "_el, storedNote);
}
Before compaction:
text ........: Specimen: Luzula sylvatica
length ......: 26
capacity ....: 96
memory usage : 113
After shrinkToFit:
text ........: Specimen: Luzula sylvatica
length ......: 26
capacity ....: 26
memory usage : 43
Stored result .: Specimen: Luzula sylvatica