aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-04-20 21:40:02 +0200
committerGitHub Enterprise <[email protected]>2026-04-20 21:40:02 +0200
commited3a02bd5bbfbca337dbc0403fa4d04b955d05ec (patch)
treef4b0f60bd1f50dc856fc1ffc2064e0b0c9cf0f6c /src/zencore
parentUse eastl::deque for queues with many small elements (#991) (diff)
downloadarchived-zen-ed3a02bd5bbfbca337dbc0403fa4d04b955d05ec.tar.xz
archived-zen-ed3a02bd5bbfbca337dbc0403fa4d04b955d05ec.zip
Add CompactString utility type (#990)
- Introduce `CompactString`: a move-only, heap-allocated, immutable string wrapper that stores its length in a prefix byte for cheap `Size()`/`ToView()` while keeping the object to a single pointer. - Swap the `ToString()` integer-formatting helpers in `zencore/string.cpp` to `std::to_chars`, which is ~5-10x faster and benefits every `IntNum` / `StringBuilder` / `CbJsonWriter` caller. - No in-tree users on `main` yet; the type is ready for callers that want owned-string storage with lower per-entry overhead than `std::string` (e.g. long-lived log buffers, session records).
Diffstat (limited to 'src/zencore')
-rw-r--r--src/zencore/include/zencore/string.h64
-rw-r--r--src/zencore/string.cpp90
2 files changed, 150 insertions, 4 deletions
diff --git a/src/zencore/include/zencore/string.h b/src/zencore/include/zencore/string.h
index b4926070c..a1c7a3914 100644
--- a/src/zencore/include/zencore/string.h
+++ b/src/zencore/include/zencore/string.h
@@ -1349,6 +1349,70 @@ std::string HideSensitiveString(std::string_view String);
//////////////////////////////////////////////////////////////////////////
+/// Owns a heap-allocated, null-terminated string buffer.
+/// Lightweight alternative to std::string when only immutable
+/// storage and string_view access are needed.
+///
+/// The buffer layout is [length-prefix][chars...][NUL]. The first
+/// byte stores the string length for strings shorter than 255 bytes,
+/// avoiding strlen() on every access. A sentinel value of 0xFF
+/// indicates a longer string; Size() then returns 255 + strlen()
+/// starting at the 256th character, skipping the known prefix.
+class CompactString
+{
+public:
+ CompactString() = default;
+
+ explicit CompactString(std::string_view Str) : m_Data(new char[1 + Str.size() + 1])
+ {
+ m_Data[0] = static_cast<char>(Str.size() < LengthFallbackSentinel ? Str.size() : LengthFallbackSentinel);
+ memcpy(m_Data + 1, Str.data(), Str.size());
+ m_Data[1 + Str.size()] = '\0';
+ }
+
+ ~CompactString() { delete[] m_Data; }
+
+ CompactString(const CompactString&) = delete;
+ CompactString& operator=(const CompactString&) = delete;
+
+ CompactString(CompactString&& Other) noexcept : m_Data(Other.m_Data) { Other.m_Data = nullptr; }
+
+ CompactString& operator=(CompactString&& Other) noexcept
+ {
+ if (this != &Other)
+ {
+ delete[] m_Data;
+ m_Data = Other.m_Data;
+ Other.m_Data = nullptr;
+ }
+ return *this;
+ }
+
+ const char* c_str() const { return m_Data ? m_Data + 1 : ""; }
+
+ size_t Size() const
+ {
+ if (!m_Data)
+ {
+ return 0;
+ }
+ uint8_t Prefix = static_cast<uint8_t>(m_Data[0]);
+ return Prefix < LengthFallbackSentinel ? Prefix : LengthFallbackSentinel + strlen(m_Data + 1 + LengthFallbackSentinel);
+ }
+
+ std::string_view ToView() const { return {c_str(), Size()}; }
+ bool IsEmpty() const { return m_Data == nullptr || m_Data[1] == '\0'; }
+
+ operator std::string_view() const { return ToView(); }
+
+private:
+ static constexpr uint8_t LengthFallbackSentinel = 0xFF;
+
+ char* m_Data = nullptr;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
void string_forcelink(); // internal
} // namespace zen
diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp
index 44f78aa75..2691d14b8 100644
--- a/src/zencore/string.cpp
+++ b/src/zencore/string.cpp
@@ -9,6 +9,7 @@
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
+#include <charconv>
#include <exception>
#include <ostream>
#include <stdexcept>
@@ -54,15 +55,21 @@ namespace zen {
bool
ToString(std::span<char> Buffer, uint64_t Num)
{
- snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, Num);
-
+ auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num);
+ if (Ec == std::errc{})
+ {
+ *Ptr = '\0';
+ }
return true;
}
bool
ToString(std::span<char> Buffer, int64_t Num)
{
- snprintf(Buffer.data(), Buffer.size(), "%" PRId64, Num);
-
+ auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num);
+ if (Ec == std::errc{})
+ {
+ *Ptr = '\0';
+ }
return true;
}
@@ -1341,6 +1348,81 @@ TEST_CASE("hidesensitivestring")
CHECK_EQ(HideSensitiveString("1234567890123456789"sv), "1234XXXX..."sv);
}
+TEST_CASE("CompactString.default")
+{
+ CompactString S;
+ CHECK(S.IsEmpty());
+ CHECK(S.Size() == 0);
+ CHECK(S.ToView() == std::string_view());
+ CHECK(S.c_str()[0] == '\0');
+}
+
+TEST_CASE("CompactString.empty")
+{
+ CompactString S(std::string_view(""));
+ CHECK(S.IsEmpty());
+ CHECK(S.Size() == 0);
+}
+
+TEST_CASE("CompactString.short")
+{
+ CompactString S(std::string_view("hello"));
+ CHECK(!S.IsEmpty());
+ CHECK(S.Size() == 5);
+ CHECK(S.ToView() == std::string_view("hello"));
+}
+
+TEST_CASE("CompactString.sentinel_boundary")
+{
+ // 254 chars — largest value that fits in the prefix byte
+ std::string Str254(254, 'x');
+ std::string_view View254(Str254);
+ CompactString S(View254);
+ CHECK(S.Size() == 254);
+ CHECK(S.ToView() == View254);
+}
+
+TEST_CASE("CompactString.sentinel_exact")
+{
+ // 255 chars — hits the 0xFF sentinel, falls back to strlen
+ std::string Str255(255, 'y');
+ std::string_view View255(Str255);
+ CompactString S(View255);
+ CHECK(S.Size() == 255);
+ CHECK(S.ToView() == View255);
+}
+
+TEST_CASE("CompactString.long")
+{
+ // Well beyond the sentinel
+ std::string Str512(512, 'z');
+ std::string_view View512(Str512);
+ CompactString S(View512);
+ CHECK(S.Size() == 512);
+ CHECK(S.ToView() == View512);
+}
+
+TEST_CASE("CompactString.move")
+{
+ CompactString A(std::string_view("test"));
+ CompactString B(std::move(A));
+ CHECK(A.IsEmpty());
+ CHECK(B.ToView() == std::string_view("test"));
+
+ CompactString C(std::string_view("first"));
+ CompactString D(std::string_view("second"));
+ D = std::move(C);
+ CHECK(C.IsEmpty());
+ CHECK(D.ToView() == std::string_view("first"));
+}
+
+TEST_CASE("CompactString.implicit_conversion")
+{
+ CompactString S(std::string_view("view"));
+ std::string_view V = S;
+ CHECK(V == std::string_view("view"));
+}
+
TEST_SUITE_END();
void