#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 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; } 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; } 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(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 uninitialized Strings!"); } // reuse const operator[], just as Scott Meyers would Char String::operator[](size_t index) { return Char(_str->index_of(&const_cast(const_cast(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!"); } 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()); // get ownership of final data return is; } std::ostream& operator<<(std::ostream& os, const String& str) { return os << str.c_str(); }