String: Add str_if_stable() as a const alternative to str()

The `str()` method must be non-const because it may need to internally
mutate the representation of the string in order to have an owned
`std::string` instance holding the exact string (not a superstring).
This is inconvenient in contexts where we can ensure that no mutation
is needed to get a `std::string const&`.

Add a `str_if_stable() const` method that returns `std::string const*`
so we can return `nullptr` if if mutation would be necessary to get a
`std::string const&`.  Add supporting `is_stable() const` and
`stabilize()` methods to check and enforce stable availability of
`std::string const&`.  These can be used to create `String const`
instances from which we can still get a `std::string const&` via
`*str_if_stable()` by maintaining the stability invariant at runtime.
This commit is contained in:
Brad King 2018-11-08 08:12:02 -05:00
parent a0841b59bd
commit 2d68b2c593
3 changed files with 97 additions and 3 deletions

View File

@ -22,20 +22,41 @@ void String::internally_mutate_to_stable_string()
*this = String(data(), size());
}
std::string const& String::str()
bool String::is_stable() const
{
return str_if_stable() != nullptr;
}
void String::stabilize()
{
if (is_stable()) {
return;
}
this->internally_mutate_to_stable_string();
}
std::string const* String::str_if_stable() const
{
if (!data()) {
// We view no string.
// This is stable for the lifetime of our current value.
return empty_string_;
return &empty_string_;
}
if (string_ && data() == string_->data() && size() == string_->size()) {
// We view an entire string.
// This is stable for the lifetime of our current value.
return *string_;
return string_.get();
}
return nullptr;
}
std::string const& String::str()
{
if (std::string const* s = str_if_stable()) {
return *s;
}
// Mutate to hold a std::string that is stable for the lifetime
// of our current value.
this->internally_mutate_to_stable_string();

View File

@ -348,6 +348,20 @@ public:
char back() const noexcept { return view_.back(); }
/** Return true if this instance is stable and otherwise false.
An instance is stable if it is in the 'null' state or if it is
an 'owned' state not produced by substring operations, or
after a call to 'stabilize()' or 'str()'. */
bool is_stable() const;
/** If 'is_stable()' does not return true, mutate so it does. */
void stabilize();
/** Get a pointer to a normal std::string if 'is_stable()' returns
true and otherwise nullptr. The pointer is valid until this
instance is mutated or destroyed. */
std::string const* str_if_stable() const;
/** Get a refernce to a normal std::string. The reference
is valid until this instance is mutated or destroyed. */
std::string const& str();

View File

@ -30,8 +30,10 @@ static bool testConstructDefault()
ASSERT_TRUE(str_const.data() == nullptr);
ASSERT_TRUE(str_const.size() == 0);
ASSERT_TRUE(str_const.empty());
ASSERT_TRUE(str_const.is_stable());
ASSERT_TRUE(str.c_str() == nullptr);
ASSERT_TRUE(str.str().empty());
ASSERT_TRUE(str.is_stable());
return true;
}
@ -42,8 +44,10 @@ static bool testFromNullPtr(cm::String str)
ASSERT_TRUE(str_const.data() == nullptr);
ASSERT_TRUE(str_const.size() == 0);
ASSERT_TRUE(str_const.empty());
ASSERT_TRUE(str_const.is_stable());
ASSERT_TRUE(str.c_str() == nullptr);
ASSERT_TRUE(str.str().empty());
ASSERT_TRUE(str.is_stable());
return true;
}
@ -68,8 +72,10 @@ static bool testFromCStrNull(cm::String str)
ASSERT_TRUE(str_const.data() == nullptr);
ASSERT_TRUE(str_const.size() == 0);
ASSERT_TRUE(str_const.empty());
ASSERT_TRUE(str_const.is_stable());
ASSERT_TRUE(str.c_str() == nullptr);
ASSERT_TRUE(str.str().empty());
ASSERT_TRUE(str.is_stable());
return true;
}
@ -96,12 +102,16 @@ static bool testFromCharArray(cm::String str)
cm::String const& str_const = str;
ASSERT_TRUE(str_const.data() != charArray);
ASSERT_TRUE(str_const.size() == sizeof(charArray) - 1);
ASSERT_TRUE(str_const.is_stable());
ASSERT_TRUE(str.c_str() != charArray);
ASSERT_TRUE(str.is_stable());
cm::String substr = str.substr(1);
cm::String const& substr_const = substr;
ASSERT_TRUE(substr_const.data() != &charArray[1]);
ASSERT_TRUE(substr_const.size() == 2);
ASSERT_TRUE(!substr_const.is_stable());
ASSERT_TRUE(substr.c_str() != &charArray[1]);
ASSERT_TRUE(!substr.is_stable());
return true;
}
@ -126,6 +136,7 @@ static bool testFromCStr(cm::String const& str)
ASSERT_TRUE(str.data() != cstr);
ASSERT_TRUE(str.size() == 3);
ASSERT_TRUE(std::strncmp(str.data(), cstr, 3) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -156,6 +167,7 @@ static bool testFromStdString(cm::String const& str)
#endif
ASSERT_TRUE(str.size() == 3);
ASSERT_TRUE(std::strncmp(str.data(), stdstr.data(), 3) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -193,6 +205,7 @@ static bool testFromChar(cm::String const& str)
{
ASSERT_TRUE(str.size() == 1);
ASSERT_TRUE(std::strncmp(str.data(), "a", 1) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -216,6 +229,7 @@ static bool testConstructFromInitList()
cm::String const str{ 'a', 'b', 'c' };
ASSERT_TRUE(str.size() == 3);
ASSERT_TRUE(std::strncmp(str.data(), "abc", 3) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -226,6 +240,7 @@ static bool testAssignFromInitList()
str = { 'a', 'b', 'c' };
ASSERT_TRUE(str.size() == 3);
ASSERT_TRUE(std::strncmp(str.data(), "abc", 3) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -243,6 +258,7 @@ static bool testConstructFromInputIterator()
ASSERT_TRUE(str.data() != cstr);
ASSERT_TRUE(str.size() == 3);
ASSERT_TRUE(std::strncmp(str.data(), cstr, 3) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -252,6 +268,7 @@ static bool testConstructFromN()
cm::String const str(3, 'a');
ASSERT_TRUE(str.size() == 3);
ASSERT_TRUE(std::strncmp(str.data(), "aaa", 3) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -262,12 +279,16 @@ static bool testFromStaticStringView(cm::String str)
cm::String const& str_const = str;
ASSERT_TRUE(str_const.data() == staticStringView.data());
ASSERT_TRUE(str_const.size() == staticStringView.size());
ASSERT_TRUE(!str_const.is_stable());
ASSERT_TRUE(str.c_str() == staticStringView);
ASSERT_TRUE(!str.is_stable());
cm::String substr = str.substr(1);
cm::String const& substr_const = substr;
ASSERT_TRUE(substr_const.data() == &staticStringView[1]);
ASSERT_TRUE(substr_const.size() == 2);
ASSERT_TRUE(!substr_const.is_stable());
ASSERT_TRUE(substr.c_str() == &staticStringView[1]);
ASSERT_TRUE(!substr.is_stable());
return true;
}
@ -294,6 +315,8 @@ static bool testConstructCopy()
ASSERT_TRUE(s1.size() == 3);
ASSERT_TRUE(s2.size() == 3);
ASSERT_TRUE(std::strncmp(s2.data(), "abc", 3) == 0);
ASSERT_TRUE(s1.is_stable());
ASSERT_TRUE(s2.is_stable());
return true;
}
@ -306,6 +329,8 @@ static bool testConstructMove()
ASSERT_TRUE(s1.size() == 0);
ASSERT_TRUE(s2.size() == 3);
ASSERT_TRUE(std::strncmp(s2.data(), "abc", 3) == 0);
ASSERT_TRUE(s1.is_stable());
ASSERT_TRUE(s2.is_stable());
return true;
}
@ -319,6 +344,8 @@ static bool testAssignCopy()
ASSERT_TRUE(s1.size() == 3);
ASSERT_TRUE(s2.size() == 3);
ASSERT_TRUE(std::strncmp(s2.data(), "abc", 3) == 0);
ASSERT_TRUE(s1.is_stable());
ASSERT_TRUE(s2.is_stable());
return true;
}
@ -332,6 +359,8 @@ static bool testAssignMove()
ASSERT_TRUE(s1.size() == 0);
ASSERT_TRUE(s2.size() == 3);
ASSERT_TRUE(std::strncmp(s2.data(), "abc", 3) == 0);
ASSERT_TRUE(s1.is_stable());
ASSERT_TRUE(s2.is_stable());
return true;
}
@ -376,6 +405,7 @@ static bool testOperatorPlusEqual()
str += cm::String("g");
ASSERT_TRUE(str.size() == 7);
ASSERT_TRUE(std::strncmp(str.data(), "abcdefg", 7) == 0);
ASSERT_TRUE(str.is_stable());
return true;
}
@ -742,15 +772,18 @@ static bool testMethod_substr_AtEnd(cm::String str)
cm::String substr = str.substr(1);
ASSERT_TRUE(substr.data() == str.data() + 1);
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(!substr.is_stable());
// c_str() at the end of the buffer does not internally mutate.
ASSERT_TRUE(std::strcmp(substr.c_str(), "bc") == 0);
ASSERT_TRUE(substr.c_str() == str.data() + 1);
ASSERT_TRUE(substr.data() == str.data() + 1);
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(!substr.is_stable());
// str() internally mutates.
ASSERT_TRUE(substr.str() == "bc");
ASSERT_TRUE(substr.is_stable());
ASSERT_TRUE(substr.data() != str.data() + 1);
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(substr.c_str() != str.data() + 1);
@ -783,9 +816,11 @@ static bool testMethod_substr_AtStart(cm::String str)
ASSERT_TRUE(substr_c != str.data());
ASSERT_TRUE(substr.data() != str.data());
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(substr.is_stable());
// str() does not need to internally mutate after c_str() did so
ASSERT_TRUE(substr.str() == "ab");
ASSERT_TRUE(substr.is_stable());
ASSERT_TRUE(substr.data() == substr_c);
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(substr.c_str() == substr_c);
@ -795,9 +830,11 @@ static bool testMethod_substr_AtStart(cm::String str)
cm::String substr = str.substr(0, 2);
ASSERT_TRUE(substr.data() == str.data());
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(!substr.is_stable());
// str() internally mutates.
ASSERT_TRUE(substr.str() == "ab");
ASSERT_TRUE(substr.is_stable());
ASSERT_TRUE(substr.data() != str.data());
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(substr.c_str() != str.data());
@ -807,6 +844,7 @@ static bool testMethod_substr_AtStart(cm::String str)
ASSERT_TRUE(std::strcmp(substr_c, "ab") == 0);
ASSERT_TRUE(substr_c == substr.data());
ASSERT_TRUE(substr.size() == 2);
ASSERT_TRUE(substr.is_stable());
}
return true;
@ -1088,6 +1126,7 @@ static bool testAddition()
cm::String str;
str += "a" + cm::String("b") + 'c';
ASSERT_TRUE(str == "abc");
ASSERT_TRUE(str.is_stable());
}
{
std::string s;
@ -1102,6 +1141,23 @@ static bool testAddition()
return true;
}
static bool testStability()
{
std::cout << "testStability()\n";
cm::String str = "abc"_s;
ASSERT_TRUE(!str.is_stable());
ASSERT_TRUE(str.str_if_stable() == nullptr);
str.stabilize();
ASSERT_TRUE(str.is_stable());
std::string const* str_if_stable = str.str_if_stable();
ASSERT_TRUE(str_if_stable != nullptr);
ASSERT_TRUE(*str_if_stable == "abc");
str.stabilize();
ASSERT_TRUE(str.is_stable());
ASSERT_TRUE(str.str_if_stable() == str_if_stable);
return true;
}
int testString(int /*unused*/, char* /*unused*/ [])
{
if (!testConstructDefault()) {
@ -1284,5 +1340,8 @@ int testString(int /*unused*/, char* /*unused*/ [])
if (!testAddition()) {
return 1;
}
if (!testStability()) {
return 1;
}
return 0;
}