#include "String.h" #include "Char.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() { 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) : 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; } String String::operator+(char c) const { auto data = new char[this->size() + 2]; // new char + null terminator std::strcpy(data, this->c_str()); data[this->size()] = c; data[this->size()+1] = '\0'; String ret; ret._str = new StringValue(data); return ret; } String& String::operator+=(char c) { auto data = new char[this->size() + 2]; // new char + null terminator std::strcpy(data, this->c_str()); data[this->size()] = c; data[this->size()+1] = '\0'; _str->operator--(); _str = new StringValue(data); return *this; } bool String::operator==(const String& other) const { if (_str) return !static_cast(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(_str))->operator[](index); // cast to use const operator[] else throw std::runtime_error("You cannot index an uninitialized String!"); } // reuse const operator[] Char String::operator[](size_t index) { return Char(_str->index_of(&const_cast(const_cast(this)->operator[](index))), *this); } size_t String::size() const { 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 an uninitialized string!"); } 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(); }