diff --git a/String.cpp b/String.cpp new file mode 100644 index 0000000..0297387 --- /dev/null +++ b/String.cpp @@ -0,0 +1,119 @@ +#include "String.h" +#include +#include + + + +String::String() +:_str(nullptr) +{} + +// string must be null-terminated +String::String(const char* 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) +{ + _str = other._str; + _str->operator++(); +} + +// copy&move-assignment operator with copy-and-swap +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() +{ + _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) : String() // init via default ctor so _str is a nullptr +{ swap(*this, other); } + +String String::operator+(const String& rhs) const +{ + auto size = this->size() + rhs.size() - 1; // minus one null-terminator, we need only one + auto data = new char[size]; // make some space + std::strcpy(data, this->c_str()); // copy data of this + String ret; + ret._str = new StringValue(std::strcat(data, rhs.c_str())); // append data of rsh + + return ret; +} + +String& String::operator+=(const String& other) +{ + auto size = this->size() + other.size() - 1; // minus one null-terminator, we need only one + auto data = new char[size]; + std::strcpy(data, this->c_str()); // copy our data + std::strcat(data, other.c_str()); // append data of the other string + + _str->operator--(); // release old data + _str = new StringValue(data); + + return *this; +} + +// TODO: continue implementing missing stuff from the middle + +bool String::operator==(const String& other) const +{ return static_cast(std::strcmp(this->c_str(), other.c_str())); } // explicit cast to silence warning + +const char& String::operator[](size_t index) const +{ return (const_cast(_str))->operator[](index); } // cast to use const operator[] + +// reuse const operator[] +char& String::operator[](size_t index) +{ return const_cast(const_cast(_str)->operator[](index)); } + +size_t String::size() const +{ return _str->size(); } + +const char* String::c_str() const +{ return *_str; } // invokes StringValue::operator const char*() + +std::istream& operator>>(std::istream& is, String& str) +{ + constexpr size_t BUFSIZE = 50; // set some reasonable buffer size + char buf[BUFSIZE]; + std::unique_ptr data(new char[1]); // istream data - I use 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 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 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()); // release ownership of final data + + return is; +} + +std::ostream& operator<<(std::ostream& os, const String& str) +{ + return os << str.c_str(); +} \ No newline at end of file diff --git a/StringValue.cpp b/StringValue.cpp new file mode 100644 index 0000000..26b8acf --- /dev/null +++ b/StringValue.cpp @@ -0,0 +1,40 @@ +#include "StringValue.h" +#include +#include + + + +StringValue::StringValue(char* data) +:_data(data), _size(std::strlen(data) + 1), _refs(1) +{} + +void StringValue::operator++() +{ _refs++; } + +void StringValue::operator--() +{ + _refs--; + if (_refs == 0) + { + delete[] _data; + delete this; + /* This might seem scary, but StringValues are dynamically allocated and own themselves, + * thus when no longer referenced the object can -- and should -- commit suicide. */ + } +} + +const char& StringValue::operator[](size_t index) const +{ + if (index > _size-1) + throw std::out_of_range("Index out of range!"); + return _data[index]; +} + +char& StringValue::operator[](size_t index) +{ return const_cast(const_cast(this)->operator[](index)); } + +StringValue::operator const char*() const +{ return _data; } + +size_t StringValue::size() const +{ return _size - 1; } \ No newline at end of file