From 88e1848fd55e9e665e4bbc59eba6024693f93333 Mon Sep 17 00:00:00 2001 From: Jonas Olsson Date: Mon, 3 Sep 2018 10:15:08 +0200 Subject: [PATCH] Add a StringBuilder class. String builder is similar to SimpleStringBuilder, but the difference is that StringBuilder is built around a std::string instance and supports dynamic resizing. Change-Id: I874d22e69e639ff9ef3d5929366f4ba71c545787 Bug: webrtc:8982 Reviewed-on: https://webrtc-review.googlesource.com/58980 Commit-Queue: Jonas Olsson Reviewed-by: Karl Wiberg Cr-Commit-Position: refs/heads/master@{#24526} --- rtc_base/BUILD.gn | 1 + rtc_base/stringencode.cc | 7 ++ rtc_base/stringencode.h | 1 + rtc_base/strings/string_builder.cc | 20 +++++ rtc_base/strings/string_builder.h | 91 +++++++++++++++++++++ rtc_base/strings/string_builder_unittest.cc | 58 +++++++++++++ 6 files changed, 178 insertions(+) diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 827559994b..498d5fea17 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -328,6 +328,7 @@ rtc_source_set("stringutils") { ":macromagic", ":safe_minmax", "../api:array_view", + "//third_party/abseil-cpp/absl/strings:strings", "//third_party/abseil-cpp/absl/types:optional", ] } diff --git a/rtc_base/stringencode.cc b/rtc_base/stringencode.cc index b4104772ea..a57845ffda 100644 --- a/rtc_base/stringencode.cc +++ b/rtc_base/stringencode.cc @@ -478,6 +478,13 @@ std::string ToString(const double d) { return std::string(&buf[0], len); } +std::string ToString(const long double d) { + char buf[32]; + const int len = std::snprintf(&buf[0], arraysize(buf), "%Lg", d); + RTC_DCHECK_LE(len, arraysize(buf)); + return std::string(&buf[0], len); +} + std::string ToString(const void* const p) { char buf[32]; const int len = std::snprintf(&buf[0], arraysize(buf), "%p", p); diff --git a/rtc_base/stringencode.h b/rtc_base/stringencode.h index 830ea77c7b..f1f802d60b 100644 --- a/rtc_base/stringencode.h +++ b/rtc_base/stringencode.h @@ -164,6 +164,7 @@ std::string ToString(long long int s); std::string ToString(unsigned long long int s); std::string ToString(double t); +std::string ToString(long double t); std::string ToString(const void* p); diff --git a/rtc_base/strings/string_builder.cc b/rtc_base/strings/string_builder.cc index 70a14da4ca..2fd4aba19c 100644 --- a/rtc_base/strings/string_builder.cc +++ b/rtc_base/strings/string_builder.cc @@ -109,4 +109,24 @@ SimpleStringBuilder& SimpleStringBuilder::Append(const char* str, return *this; } +StringBuilder& StringBuilder::AppendFormat(const char* fmt, ...) { + va_list args, copy; + va_start(args, fmt); + va_copy(copy, args); + const int predicted_length = std::vsnprintf(nullptr, 0, fmt, copy); + va_end(copy); + + RTC_DCHECK_GE(predicted_length, 0); + if (predicted_length > 0) { + const size_t size = str_.size(); + str_.resize(size + predicted_length); + // Pass "+ 1" to vsnprintf to include space for the '\0'. + const int actual_length = + std::vsnprintf(&str_[size], predicted_length + 1, fmt, args); + RTC_DCHECK_GE(actual_length, 0); + } + va_end(args); + return *this; +} + } // namespace rtc diff --git a/rtc_base/strings/string_builder.h b/rtc_base/strings/string_builder.h index 093954b361..d95b29decb 100644 --- a/rtc_base/strings/string_builder.h +++ b/rtc_base/strings/string_builder.h @@ -15,9 +15,11 @@ #include #include +#include "absl/strings/string_view.h" #include "api/array_view.h" #include "rtc_base/checks.h" #include "rtc_base/numerics/safe_minmax.h" +#include "rtc_base/stringencode.h" #include "rtc_base/stringutils.h" namespace rtc { @@ -82,6 +84,95 @@ class SimpleStringBuilder { size_t size_ = 0; }; +// A string builder that supports dynamic resizing while building a string. +// The class is based around an instance of std::string and allows moving +// ownership out of the class once the string has been built. +// Note that this class uses the heap for allocations, so SimpleStringBuilder +// might be more efficient for some use cases. +class StringBuilder { + public: + StringBuilder() {} + explicit StringBuilder(absl::string_view s) : str_(s) {} + + // TODO(tommi): Support construction from StringBuilder? + StringBuilder(const StringBuilder&) = delete; + StringBuilder& operator=(const StringBuilder&) = delete; + + StringBuilder& operator<<(const absl::string_view str) { + str_.append(str.data(), str.length()); + return *this; + } + + StringBuilder& operator<<(char c) = delete; + + StringBuilder& operator<<(int i) { + str_ += rtc::ToString(i); + return *this; + } + + StringBuilder& operator<<(unsigned i) { + str_ += rtc::ToString(i); + return *this; + } + + StringBuilder& operator<<(long i) { // NOLINT + str_ += rtc::ToString(i); + return *this; + } + + StringBuilder& operator<<(long long i) { // NOLINT + str_ += rtc::ToString(i); + return *this; + } + + StringBuilder& operator<<(unsigned long i) { // NOLINT + str_ += rtc::ToString(i); + return *this; + } + + StringBuilder& operator<<(unsigned long long i) { // NOLINT + str_ += rtc::ToString(i); + return *this; + } + + StringBuilder& operator<<(float f) { + str_ += rtc::ToString(f); + return *this; + } + + StringBuilder& operator<<(double f) { + str_ += rtc::ToString(f); + return *this; + } + + StringBuilder& operator<<(long double f) { + str_ += rtc::ToString(f); + return *this; + } + + const std::string& str() const { return str_; } + + void Clear() { str_.clear(); } + + size_t size() const { return str_.size(); } + + std::string Release() { + std::string ret; + std::swap(str_, ret); + return ret; + } + + // Allows appending a printf style formatted string. + StringBuilder& AppendFormat(const char* fmt, ...) +#if defined(__GNUC__) + __attribute__((__format__(__printf__, 2, 3))) +#endif + ; + + private: + std::string str_; +}; + } // namespace rtc #endif // RTC_BASE_STRINGS_STRING_BUILDER_H_ diff --git a/rtc_base/strings/string_builder_unittest.cc b/rtc_base/strings/string_builder_unittest.cc index aa570ebbb8..878e128b19 100644 --- a/rtc_base/strings/string_builder_unittest.cc +++ b/rtc_base/strings/string_builder_unittest.cc @@ -140,4 +140,62 @@ TEST(SimpleStringBuilder, BufferOverrunIntAlreadyFull) { #endif +//////////////////////////////////////////////////////////////////////////////// +// StringBuilder. + +TEST(StringBuilder, Limit) { + StringBuilder sb; + EXPECT_EQ(0u, sb.str().size()); + + sb << "012345678"; + EXPECT_EQ(sb.str(), "012345678"); +} + +TEST(StringBuilder, NumbersAndChars) { + StringBuilder sb; + sb << 1 << ":" << 2.1 << ":" << 2.2f << ":" << 78187493520ll << ":" + << 78187493520ul; + EXPECT_THAT(sb.str(), + testing::MatchesRegex("1:2.10*:2.20*:78187493520:78187493520")); +} + +TEST(StringBuilder, Format) { + StringBuilder sb; + sb << "Here we go - "; + sb.AppendFormat("This is a hex formatted value: 0x%08llx", 3735928559ULL); + EXPECT_EQ(sb.str(), "Here we go - This is a hex formatted value: 0xdeadbeef"); +} + +TEST(StringBuilder, StdString) { + StringBuilder sb; + std::string str = "does this work?"; + sb << str; + EXPECT_EQ(str, sb.str()); +} + +TEST(StringBuilder, Release) { + StringBuilder sb; + std::string str = + "This string has to be of a moderate length, or we might " + "run into problems with small object optimizations."; + EXPECT_LT(sizeof(str), str.size()); + sb << str; + EXPECT_EQ(str, sb.str()); + const char* original_buffer = sb.str().c_str(); + std::string moved = sb.Release(); + EXPECT_TRUE(sb.str().empty()); + EXPECT_EQ(str, moved); + EXPECT_EQ(original_buffer, moved.c_str()); +} + +TEST(StringBuilder, Reset) { + StringBuilder sb("abc"); + sb << "def"; + EXPECT_EQ("abcdef", sb.str()); + sb.Clear(); + EXPECT_TRUE(sb.str().empty()); + sb << 123 << "!"; + EXPECT_EQ("123!", sb.str()); +} + } // namespace rtc