refcountedstring/String.cpp

180 lines
5.4 KiB
C++

#include "String.h"
String::String() noexcept
:_str(nullptr)
{}
// string must be null-terminated
String::String(const char* string)
:String()
{
auto size = std::strlen(string) + 1; // make space for null-terminator
auto data = new char[size];
std::strcpy(data, string);
_str = new StringValue(data); // initialize new StringValue with data
}
String::String(const String& other) noexcept
{
_str = other._str;
_str->operator++();
}
// copy&move-assignment operator with copy-and-swap
// provides strong exception guarantee
String& String::operator=(String other) // take parameter by value: can bind to copy and move-assignment
{ // & the compiler can optimize copying
swap(*this, other);
return *this;
}
String::~String() noexcept
{
if (_str)
_str->operator--(); // the ref-counting is done by StringValue, no need to do that here
}
void swap(String& s1, String& s2) noexcept
{
using std::swap; // enable ADL, not necessary in our case, yet good practice
swap(s1._str, s2._str);
}
String::String(String&& other) noexcept
:String() // init via default ctor so _str is a nullptr
{ swap(*this, other); }
String String::operator+(const String& rhs) const
{
auto data = this->copy_and_concat(rhs);
String ret;
ret._str = new StringValue(data); // build new String from data
return ret;
}
// provides strong exception guarantee
String& String::operator+=(const String& rhs)
{
auto data = this->copy_and_concat(rhs);
_str->operator--(); // release old data
_str = new StringValue(data); // create new
return *this;
}
// copies this and concats str
char* String::copy_and_concat(const String& str) const
{
auto size = this->size() + str.size() + 1; // plus a null-terminator
auto data = new char[size]; // make some space
std::strcpy(data, this->c_str()); // copy data of this
std::strcat(data, str.c_str()); // concat str
return data;
}
String String::operator+(char rhs) const
{
auto data = this->copy_and_concat(rhs);
String ret;
ret._str = new StringValue(data); // build new String from data
return ret;
}
// provides strong exception guarantee
String& String::operator+=(char rhs)
{
auto data = this->copy_and_concat(rhs);
_str->operator--(); // release old data
_str = new StringValue(data); // create new
return *this;
}
// copies this and concats c
char* String::copy_and_concat(char c) const
{
auto data = new char[this->size() + 2]; // plus new char & null terminator
std::strcpy(data, this->c_str()); // copy data of this
data[this->size()] = c; // concats c
data[this->size() + 1] = '\0'; // null-terminator
return data;
}
bool String::operator==(const String& other) const
{
if (_str)
return !static_cast<bool>(std::strcmp(this->c_str(), other.c_str())); // explicit cast to silence warning
else
throw std::runtime_error("You cannot compare uninitialized Strings!");
}
const char& String::operator[](size_t index) const
{
if (_str)
return (const_cast<const StringValue*>(_str))->operator[](index); // cast to use const operator[]
else
throw std::runtime_error("You cannot uninitialized Strings!");
}
// reuse const operator[], just as Scott Meyers would
Char String::operator[](size_t index)
{ return Char(_str->index_of(&const_cast<char&>(const_cast<const String*>(this)->operator[](index))), *this); }
size_t String::size() const noexcept
{
if (_str)
return _str->size();
else
return 0;
}
const char* String::c_str() const
{
if (_str)
return *_str; // invokes StringValue::operator const char*()
else
throw std::runtime_error("You cannot get the content of uninitialized Strings!");
}
// provides strong exception guarantee
std::istream& operator>>(std::istream& is, String& str)
{
constexpr size_t BUFSIZE = 50; // set some reasonable buffer size
char buf[BUFSIZE];
std::unique_ptr<char[]> data(new char[1]); // istream data - unique_ptr for exception safety
data.get()[0] = '\0'; // init it with a null terminator
while (is.get(buf, BUFSIZE)) // while there is still some data in the stream
{
std::unique_ptr<char[]> newdata(new char[strlen(data.get()) + BUFSIZE]); // make some space
std::strcpy(newdata.get(), data.get()); // copy the data already read
data.swap(newdata); // make the old data go out of scope instead of newdata
std::strcat(data.get(), buf); // append new input
}
std::unique_ptr<char[]> newdata(new char[strlen(data.get()) + 1]); // shrink the array
std::strcpy(newdata.get(), data.get()); // to fit the data
data.swap(newdata);
if (str._str) // if there is some old data, release it
str._str->operator--();
str._str = new StringValue(data.release()); // set owner of final data
return is;
}
std::ostream& operator<<(std::ostream& os, const String& str)
{
return os << str.c_str();
}