C++ string class implementation, providing support for various C++11 features












3














This is my implementation of a string class similar to std::string and supports the following:




  1. range-for loops

  2. iterators

  3. basic utility functions

  4. exponential capacity increase

  5. operator overloads


I am looking for specific reviews on implementation of iterators, exponential capacity increase, usage of noexcept and implementations of some functions like resize(), operator+=, operator=, and operator+.



mystring.h



#ifndef MY_STRING_H_
#define MY_STRING_H_

#include <cstring>
#include <iostream>
#include <memory>
#include <algorithm>
#include <stdexcept>
#include <limits>

namespace kapil {
class string final {
private:
// default capacity of empty string.
//Minimum default_capacity is 2.
static constexpr size_t default_capacity_ = 16;
// current capacity of the string container.
size_t current_capacity_;
//size of string.
size_t sz_;
// pointer to character
std::unique_ptr<char> ptr_;
public:
string();
string(const string&);
string(string&&) noexcept;
string(const char*);
explicit string(char);
~string() noexcept;

size_t capacity() const noexcept;
size_t size() const noexcept;
size_t length() const noexcept;
void resize(size_t, char ch = '');
void clear() noexcept;
bool empty() const noexcept;
char& at(size_t);
const char& at(size_t) const;
char& back();
const char& back() const;
char& front();
const char& front() const;
string& append(const string&);
string& append(const char*);
string& append(string&&);
string& append(char);
void push_back(char);
string& assign(const string&);
string& assign(const char*);
string& assign(string&&);
void swap(string&);
const char* c_str() const noexcept;
const char* data() const noexcept;



string& operator = (const string&);
string& operator = (string&&) noexcept;
string& operator = (const char*);
string& operator = (char);
string& operator += (const string&);
string& operator += (const char*);
string& operator += (char);
string& operator += (string&&);
char& operator (size_t);
const char& operator (size_t) const;

friend std::ostream& operator << (std::ostream&, const string&);
friend string operator + (const string&, const string&);
friend string operator + (const string&, const char*);
friend string operator + (const char*, const string&);
friend string operator + (string&&, string&&);
friend string operator + (string&&, const char*);
friend string operator + (const char*, string&&);
friend string operator + (const string&, string&&);
friend string operator + (string&&, const string&);
friend string operator + (const string&, char);
friend string operator + (char, const string&);
friend string operator + (string&&, char);
friend string operator + (char, string&&);
friend bool operator == (const string&, const string&) noexcept;
friend bool operator == (const string&, const char*) noexcept;
friend bool operator == (const char*, const string&) noexcept;
friend bool operator == (const string&, char) noexcept;
friend bool operator == (char, const string&) noexcept;
friend bool operator == (const string&, string&&) noexcept;
friend bool operator == (string&&, const string&) noexcept;
friend bool operator == (string&&, string&&) noexcept;
friend bool operator == (string&&, char) noexcept;
friend bool operator == (char, string&&) noexcept;
friend bool operator == (const char*, string&&) noexcept;
friend bool operator == (string&&, const char*) noexcept;
friend bool operator != (const string&, const string&) noexcept;
friend bool operator != (const string&, const char*) noexcept;
friend bool operator != (const char*, const string&) noexcept;
friend bool operator != (const string&, char) noexcept;
friend bool operator != (char, const string&) noexcept;
friend bool operator != (const string&, string&&) noexcept;
friend bool operator != (string&&, const string&) noexcept;
friend bool operator != (string&&, string&&) noexcept;
friend bool operator != (string&&, char) noexcept;
friend bool operator != (char, string&&) noexcept;
friend bool operator != (const char*, string&&) noexcept;
friend bool operator != (string&&, const char*) noexcept;

class iterator
{
private:
string* str_;
size_t index_;
public:
iterator(string* = nullptr, size_t = 0) noexcept;
iterator(const iterator&) noexcept;
iterator(iterator&&) noexcept;
~iterator() noexcept;

iterator& operator = (const iterator&) noexcept;
iterator& operator = (iterator&&) noexcept;
bool operator != (const iterator&) const noexcept;
bool operator == (const iterator&) const noexcept;
iterator& operator ++ () noexcept;
iterator& operator ++ (int) noexcept;
iterator& operator -- () noexcept;
iterator& operator -- (int) noexcept;
char& operator * () const;
};

iterator begin();
iterator end();

class const_iterator
{
private:
const string* str_;
size_t index_;
public:
const_iterator(const string*, size_t) noexcept;
const_iterator(const const_iterator&) noexcept;
const_iterator(const_iterator&&) noexcept;
~const_iterator() noexcept;

const_iterator& operator = (const const_iterator&) noexcept;
const_iterator& operator = (const_iterator&&) noexcept;
bool operator != (const const_iterator&) const noexcept;
bool operator == (const const_iterator&) const noexcept;
const_iterator& operator ++ () noexcept;
const_iterator& operator ++ (int) noexcept;
const_iterator& operator -- () noexcept;
const_iterator& operator -- (int) noexcept;
const char& operator * () const;
};

const_iterator cbegin();
const_iterator cend();

class reverse_iterator
{
private:
string* str_;
size_t index_;
public:
reverse_iterator(string* = nullptr, size_t = 0) noexcept;
reverse_iterator(const reverse_iterator&) noexcept;
reverse_iterator(reverse_iterator&&) noexcept;
~reverse_iterator() noexcept;

reverse_iterator& operator = (const reverse_iterator&) noexcept;
reverse_iterator& operator = (reverse_iterator&&) noexcept;
bool operator != (const reverse_iterator&) const noexcept;
bool operator == (const reverse_iterator&) const noexcept;
reverse_iterator& operator ++ () noexcept;
reverse_iterator& operator ++ (int) noexcept;
reverse_iterator& operator -- () noexcept;
reverse_iterator& operator -- (int) noexcept;
char& operator * () const;
};

reverse_iterator rbegin();
reverse_iterator rend();

class reverse_const_iterator
{
private:
const string* str_;
size_t index_;
public:
reverse_const_iterator(const string*, size_t) noexcept;
reverse_const_iterator(const reverse_const_iterator&) noexcept;
reverse_const_iterator(reverse_const_iterator&&) noexcept;
~reverse_const_iterator() noexcept;

reverse_const_iterator& operator = (const reverse_const_iterator&) noexcept;
reverse_const_iterator& operator = (reverse_const_iterator&&) noexcept;
bool operator != (const reverse_const_iterator&) const noexcept;
bool operator == (const reverse_const_iterator&) const noexcept;
reverse_const_iterator& operator ++ () noexcept;
reverse_const_iterator& operator ++ (int) noexcept;
reverse_const_iterator& operator -- () noexcept;
reverse_const_iterator& operator -- (int) noexcept;
const char& operator * () const;
};

reverse_const_iterator crbegin();
reverse_const_iterator crend();
};
} //kapil

#endif


my_string.cpp



#include "my_string.h"

namespace kapil {
/*
For the given new_string_length, the appropriate capacity is the
next power of 2 that is greater than new_string_length.
*/
size_t get_appropriate_capacity(size_t new_string_length) {
size_t appropriate_capacity = 16;
if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
appropriate_capacity = new_string_length;
} else {
appropriate_capacity = 16;
if (appropriate_capacity <= new_string_length) {
if (!(new_string_length & (new_string_length - 1))) {
appropriate_capacity = new_string_length << 1;
} else {
while (appropriate_capacity < new_string_length) {
appropriate_capacity <<= 1;
}
}
}
}
return appropriate_capacity;
}

/**************************************** member functions *********************/

string::string()
: current_capacity_{ default_capacity_ } {
sz_ = 0;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
ptr_.get()[0] = '';
}

string::string(const string& other) {
current_capacity_ = other.current_capacity_;
sz_ = other.sz_;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
std::strcpy(ptr_.get(), other.ptr_.get());
}

string::string(string&& rval) noexcept
: current_capacity_{ rval.current_capacity_ },
sz_{ rval.sz_ },
ptr_{ std::move(rval.ptr_) } {
}

string::string(const char* c_string) {
sz_ = std::strlen(c_string);
current_capacity_ = get_appropriate_capacity(sz_);
ptr_ = std::make_unique<char>(current_capacity_ + 1);
std::strcpy(ptr_.get(), c_string);
}

string::string(char ch) {
sz_ = 1;
current_capacity_ = default_capacity_;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
ptr_.get()[0] = ch;
ptr_.get()[1] = '';
}

string::~string() noexcept {
current_capacity_ = 0;
sz_ = 0;
ptr_.reset(nullptr);
};


/**************************************** member functions *********************/

size_t string::capacity() const noexcept {
return current_capacity_;
}

size_t string::size() const noexcept {
return sz_;
}

size_t string::length() const noexcept {
return sz_;
}

void string::resize(size_t n, char ch) {
if (n == sz_) {
return;
}

size_t appropriate_capacity = get_appropriate_capacity(n);

std::unique_ptr<char> temp;
auto resized = bool{false};

if (current_capacity_ != appropriate_capacity) {
resized = true;
current_capacity_ = appropriate_capacity;
temp = std::make_unique<char>(current_capacity_ + 1);
}

if (n < sz_) {
if (resized) {
std::strncpy(temp.get(), ptr_.get(), n);
temp.get()[n] = '';
} else {
ptr_.get()[n] = '';
}
} else if (n > sz_) {
if (resized) {
std::strncpy(temp.get(), ptr_.get(), sz_);
std::fill(temp.get() + sz_, temp.get() + n, ch);
temp.get()[n] = '';
} else {
std::fill(ptr_.get() + sz_, ptr_.get() + n, ch);
ptr_.get()[n] = '';
}
}

sz_ = n;
if (resized) {
ptr_ = std::move(temp);
}
}

void string::clear() noexcept {
current_capacity_ = default_capacity_;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
ptr_.get()[0] = '';
sz_ = 0;
}

bool string::empty() const noexcept {
return sz_ == 0;
}

char& string::at(size_t idx) {
if (idx < 0 || idx >= sz_) {
throw std::out_of_range{"out of range memory access"};
}
return (*this)[idx];
}

const char& string::at(size_t idx) const {
if (idx < 0 || idx >= sz_) {
throw std::out_of_range{"out of range memory access"};
}
return (*this)[idx];
}

char& string::back() {
return (*this)[sz_ - 1];
}

const char& string::back() const {
return (*this)[sz_ - 1];
}

char& string::front() {
return (*this)[0];
}

const char& string::front() const {
return (*this)[0];
}

string& string::append(const string& rhs) {
(*this) += rhs;
return *this;
}

string& string::append(const char* rhs) {
(*this) += rhs;
return *this;
}

string& string::append(string&& rhs) {
(*this) += rhs;
return *this;
}

string& string::append(char ch) {
(*this) += ch;
return *this;
}

void string::push_back(char ch) {
(*this) += ch;
return;
}

string& string::assign(const string& rhs) {
(*this) = rhs;
return *this;
}

string& string::assign(const char* rhs) {
(*this) = rhs;
return *this;
}

string& string::assign(string&& rhs) {
(*this) = rhs;
return *this;
}

void string::swap(string &str) {
string temp{str};
str = *this;
*this = temp;
}

const char* string::c_str() const noexcept {
return ptr_.get();
}

const char* string::data() const noexcept {
return c_str();
}



/**************************************** member operator overloads*********************/

string& string::operator = (const string& rhs) {
if (this != &rhs) {
if (current_capacity_ != rhs.current_capacity_) {
current_capacity_ = rhs.current_capacity_;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
}
sz_ = rhs.sz_;
std::strcpy(ptr_.get(), rhs.c_str());
}
return *this;
}

string& string::operator = (string&& rval) noexcept {
current_capacity_ = rval.current_capacity_;
sz_ = rval.sz_;
ptr_ = std::move(rval.ptr_);
return *this;
}

string& string::operator = (const char* c_string) {
sz_ = std::strlen(c_string);
auto appropriate_capacity = get_appropriate_capacity(sz_);
if (current_capacity_ != appropriate_capacity) {
current_capacity_ = appropriate_capacity;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
}
std::strcpy(ptr_.get(), c_string);
return *this;
}

string& string::operator = (char ch) {
current_capacity_ = default_capacity_;
sz_ = 1;
ptr_ = std::make_unique<char>(current_capacity_ + 1);
ptr_.get()[0] = ch;
ptr_.get()[1] = '';
return *this;
}

string& string::operator += (const string& rhs) {
std::unique_ptr<char> temp;
auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs.sz_);

if (current_capacity_ != appropriate_capacity) {
current_capacity_ = appropriate_capacity;
temp = std::make_unique<char>(current_capacity_ + 1);
std::strcpy(temp.get(), ptr_.get());
ptr_ = std::move(temp);
}

std::strcpy(ptr_.get() + sz_, rhs.c_str());
sz_ += rhs.sz_;

return *this;
}

string& string::operator += (const char* rhs) {
std::unique_ptr<char> temp;
auto rhs_sz = std::strlen(rhs);
auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs_sz);

if (current_capacity_ != appropriate_capacity) {
current_capacity_ = appropriate_capacity;
temp = std::make_unique<char>(current_capacity_ + 1);
std::strcpy(temp.get(), ptr_.get());
ptr_ = std::move(temp);
}

std::strcpy(ptr_.get() + sz_, rhs);
sz_ += rhs_sz;

return *this;
}

string& string::operator += (char ch) {
auto appropriate_capacity = get_appropriate_capacity(sz_ + 1);
std::unique_ptr<char> temp;

if (current_capacity_ != appropriate_capacity) {
current_capacity_ = appropriate_capacity;
temp = std::make_unique<char>(current_capacity_ + 1);
strcpy(temp.get(), ptr_.get());
ptr_ = std::move(temp);
}
ptr_.get()[sz_] = ch;
ptr_.get()[sz_ + 1] = '';
sz_ += 1;

return *this;
}


string& string::operator += (string&& rval) {
std::unique_ptr<char> temp;
auto appropriate_capacity = get_appropriate_capacity(sz_ + rval.sz_);

if (current_capacity_ != appropriate_capacity) {
current_capacity_ = appropriate_capacity;
temp = std::make_unique<char>(current_capacity_ + 1);
std::strcpy(temp.get(), ptr_.get());
ptr_ = std::move(temp);
}

std::strcpy(ptr_.get() + sz_, rval.c_str());
sz_ += rval.sz_;

return *this;
}

char& string::operator (size_t idx) {
return ptr_.get()[idx];
}

const char& string::operator (size_t idx) const {
return ptr_.get()[idx];
}



/**************************************** friend operator overloads *********************/

std::ostream& operator << (std::ostream& out, const string& str) {
if (str.size() > 0) {
out.write(str.c_str(), str.size());
}
return out;
}

string operator + (const string& lhs, const string& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (const string& lhs, const char* rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (const char* lhs, const string& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (string&& lhs, string&& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (string&& lhs, const char* rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (const char* lhs, string&& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (const string& lhs, string&& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (string&& lhs, const string& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (const string& lhs, char rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (char lhs, const string& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (string&& lhs, char rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

string operator + (char lhs, string&& rhs) {
string temp{lhs};
temp += rhs;
return temp;
}

bool operator == (const string& lhs, const string& rhs) noexcept {
return (lhs.sz_ == rhs.sz_) &&
((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
}

bool operator == (const string& lhs, const char* rhs) noexcept {
return (lhs.sz_ == std::strlen(rhs)) &&
((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
}

bool operator == (const char* lhs, const string& rhs) noexcept {
return (strlen(lhs) == rhs.sz_) &&
((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
}

bool operator == (const string& lhs, char rhs) noexcept {
return (lhs.sz_ == 1) &&
(lhs.ptr_.get()[0] == rhs);
}

bool operator == (char lhs, const string& rhs) noexcept {
return (rhs.sz_ == 1) &&
(lhs == rhs.ptr_.get()[0]);
}

bool operator == (const string& lhs, string&& rhs) noexcept {
return (lhs.sz_ == rhs.sz_) &&
((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
}

bool operator == (string&& lhs, const string& rhs) noexcept {
return (lhs.sz_ == rhs.sz_) &&
((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
}

bool operator == (string&& lhs, string&& rhs) noexcept {
return (lhs.sz_ == rhs.sz_) &&
((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
}

bool operator == (string&& lhs, char rhs) noexcept {
return (lhs.sz_ == 1) &&
(lhs.ptr_.get()[0] == rhs);
}

bool operator == (char lhs, string&& rhs) noexcept {
return (rhs.sz_ == 1) &&
(rhs.ptr_.get()[0] == lhs);
}

bool operator == (string&& lhs, const char* rhs) noexcept {
return (lhs.sz_ == std::strlen(rhs)) &&
((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
}

bool operator == (const char* lhs, string && rhs) noexcept {
return (std::strlen(lhs) == rhs.sz_) &&
((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
}

bool operator != (const string& lhs, const string& rhs) noexcept {
return !(lhs == rhs);
}

bool operator != (const string& lhs, const char* rhs) noexcept {
return !(lhs == rhs);
}

bool operator != (const char* lhs, const string& rhs) noexcept {
return !(lhs == rhs);
}

bool operator != (const string& lhs, char rhs) noexcept {
return !(lhs == rhs);
}

bool operator != (char lhs, const string& rhs) noexcept {
return !(lhs == rhs);
}

bool operator != (const string& lhs, string&& rhs) noexcept {
return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
}

bool operator != (string&& lhs, const string& rhs) noexcept {
return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
}

bool operator != (string&& lhs, string&& rhs) noexcept {
return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
}

bool operator != (string&& lhs, char rhs) noexcept {
return (lhs.sz_ != 1) || (lhs.ptr_.get()[0] != rhs);
}

bool operator != (char lhs, string&& rhs) noexcept {
return (rhs.sz_ != 1) || (rhs.ptr_.get()[0] != lhs);
}

bool operator != (string&& lhs, const char* rhs) noexcept {
return (lhs.sz_ != std::strlen(rhs)) || (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) != 0);
}

bool operator != (const char* lhs, string && rhs) noexcept {
return (std::strlen(lhs) != rhs.sz_) || (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) != 0);
}



/**************************************** iterator related implementations *********************/

using iterator = string::iterator;

iterator::iterator(string *str, size_t index) noexcept
: str_{str}, index_{index} {
}

iterator::iterator(const iterator& itr) noexcept
: str_{itr.str_}, index_{itr.index_} {
}

iterator::iterator(iterator&& rval) noexcept
: str_{rval.str_}, index_{rval.index_} {
}

iterator::~iterator() noexcept {
str_ = nullptr;
index_ = 0;
}



iterator& iterator::operator = (const iterator& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

iterator& iterator::operator = (iterator&& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

bool iterator::operator != (const iterator& rhs) const noexcept {
return (str_ != rhs.str_) || (index_ != rhs.index_);
}

bool iterator::operator == (const iterator& rhs) const noexcept {
return (str_ == rhs.str_) && (index_ == rhs.index_);
}

iterator& iterator::operator ++ () noexcept {
++index_;
return *this;
}

iterator& iterator::operator ++ (int dummy) noexcept {
++(*this);
return *this;
}

iterator& iterator::operator -- () noexcept {
--index_;
return *this;
}

iterator& iterator::operator -- (int dummy) noexcept {
--(*this);
return *this;
}

char& iterator::operator * () const {
return (*str_)[index_];
}

iterator string::begin() {
return iterator(this);
}

iterator string::end() {
return iterator(this, sz_);
}



using const_iterator = string::const_iterator;

const_iterator::const_iterator(const string* str, size_t index) noexcept
: str_{str}, index_{index} {
}

const_iterator::const_iterator(const const_iterator& itr) noexcept
: str_{itr.str_}, index_{itr.index_} {
}

const_iterator::const_iterator(const_iterator&& rval) noexcept
: str_{rval.str_}, index_{rval.index_} {
}

const_iterator::~const_iterator() noexcept {
str_ = nullptr;
index_ = 0;
}



const_iterator& const_iterator::operator = (const const_iterator& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

const_iterator& const_iterator::operator = (const_iterator&& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

bool const_iterator::operator != (const const_iterator& rhs) const noexcept {
return (str_ != rhs.str_) || (index_ != rhs.index_);
}

bool const_iterator::operator == (const const_iterator& rhs) const noexcept {
return (str_ == rhs.str_) && (index_ == rhs.index_);
}

const_iterator& const_iterator::operator ++ () noexcept {
++index_;
return *this;
}

const_iterator& const_iterator::operator ++ (int dummy) noexcept {
++(*this);
return *this;
}

const_iterator& const_iterator::operator -- () noexcept {
--index_;
return *this;
}

const_iterator& const_iterator::operator -- (int dummy) noexcept {
--(*this);
return *this;
}

const char& const_iterator::operator * () const {
return (*str_)[index_];
}

const_iterator string::cbegin() {
return const_iterator(this, 0);
}

const_iterator string::cend() {
return const_iterator(this, sz_);
}



using reverse_iterator = string::reverse_iterator;

reverse_iterator::reverse_iterator(string *str, size_t index) noexcept
: str_{str}, index_{index} {
}

reverse_iterator::reverse_iterator(const reverse_iterator& itr) noexcept
: str_{itr.str_}, index_{itr.index_} {
}

reverse_iterator::reverse_iterator(reverse_iterator&& rval) noexcept
: str_{rval.str_}, index_{rval.index_} {
}

reverse_iterator::~reverse_iterator() noexcept {
str_ = nullptr;
index_ = 0;
}



reverse_iterator& reverse_iterator::operator = (const reverse_iterator& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

reverse_iterator& reverse_iterator::operator = (reverse_iterator&& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

bool reverse_iterator::operator != (const reverse_iterator& rhs) const noexcept {
return (str_ != rhs.str_) || (index_ != rhs.index_);
}

bool reverse_iterator::operator == (const reverse_iterator& rhs) const noexcept {
return (str_ == rhs.str_) && (index_ == rhs.index_);
}

reverse_iterator& reverse_iterator::operator ++ () noexcept {
--index_;
return *this;
}

reverse_iterator& reverse_iterator::operator ++ (int dummy) noexcept {
++(*this);
return *this;
}

reverse_iterator& reverse_iterator::operator -- () noexcept {
++index_;
return *this;
}

reverse_iterator& reverse_iterator::operator -- (int dummy) noexcept {
--(*this);
return *this;
}

char& reverse_iterator::operator * () const {
return (*str_)[index_];
}

reverse_iterator string::rbegin() {
return reverse_iterator(this, sz_ - 1);
}

reverse_iterator string::rend() {
return reverse_iterator(this, -1);
}



using reverse_const_iterator = string::reverse_const_iterator;

reverse_const_iterator::reverse_const_iterator(const string* str, size_t index) noexcept
: str_{str}, index_{index} {
}

reverse_const_iterator::reverse_const_iterator(const reverse_const_iterator& itr) noexcept
: str_{itr.str_}, index_{itr.index_} {
}

reverse_const_iterator::reverse_const_iterator(reverse_const_iterator&& rval) noexcept
: str_{rval.str_}, index_{rval.index_} {
}

reverse_const_iterator::~reverse_const_iterator() noexcept {
str_ = nullptr;
index_ = 0;
}



reverse_const_iterator& reverse_const_iterator::operator = (const reverse_const_iterator& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

reverse_const_iterator& reverse_const_iterator::operator = (reverse_const_iterator&& rhs) noexcept {
str_ = rhs.str_;
index_ = rhs.index_;
return *this;
}

bool reverse_const_iterator::operator != (const reverse_const_iterator& rhs) const noexcept {
return (str_ != rhs.str_) || (index_ != rhs.index_);
}

bool reverse_const_iterator::operator == (const reverse_const_iterator& rhs) const noexcept {
return (str_ == rhs.str_) && (index_ == rhs.index_);
}

reverse_const_iterator& reverse_const_iterator::operator ++ () noexcept {
--index_;
return *this;
}

reverse_const_iterator& reverse_const_iterator::operator ++ (int dummy) noexcept {
++(*this);
return *this;
}

reverse_const_iterator& reverse_const_iterator::operator -- () noexcept {
++index_;
return *this;
}

reverse_const_iterator& reverse_const_iterator::operator -- (int dummy) noexcept {
--(*this);
return *this;
}

const char& reverse_const_iterator::operator * () const {
return (*str_)[index_];
}

reverse_const_iterator string::crbegin() {
return reverse_const_iterator(this, sz_ - 1);
}

reverse_const_iterator string::crend() {
return reverse_const_iterator(this, -1);
}
} //kapil


Here is a test file which shows the usage:



test_string.cpp



#include "my_string.h"

using namespace kapil;

int main() {
string x{"kapil"};
std::cout << " size : " << x.size() << " length : " << x.length() << " is empty : " << x.empty() << " at 2 : " << x.at(2) << " back : " <<
x.back() << " c_str : " << x.c_str() << " data : " << x.data() << std::endl;
x.clear();
x = "dev";
string y{" singh"};
x.append(y);
x.append(" is");
y.assign(" assigned");
x += " operator +";
x += y;

std::cout << " x : " << x << " x cap : " << x.capacity() << "n y : " << y << " y cap : " << y.capacity() << std::endl;
string added = "i am binary + " + y + string{" ravl add "} + 'x';
std::cout << " added : " << added << " added cap : " << added.capacity() << std::endl;
added = "kapil";
added.resize(10, 'k');
std::cout << " added resize 10 : " << added << " added cap : " << added.capacity() << std::endl;
added.resize(78, 'l');
std::cout << " added resize 78 : " << added << " added cap : " << added.capacity() << std::endl;
string s1 = "kapil";
s1.swap(added);
std::cout << " added : " << added << " s1 : " << s1 << std::endl;

for (auto it : s1) {
std::cout << it << " ";
}

std::cout << "n";
return 0;
}









share|improve this question









New contributor




kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

























    3














    This is my implementation of a string class similar to std::string and supports the following:




    1. range-for loops

    2. iterators

    3. basic utility functions

    4. exponential capacity increase

    5. operator overloads


    I am looking for specific reviews on implementation of iterators, exponential capacity increase, usage of noexcept and implementations of some functions like resize(), operator+=, operator=, and operator+.



    mystring.h



    #ifndef MY_STRING_H_
    #define MY_STRING_H_

    #include <cstring>
    #include <iostream>
    #include <memory>
    #include <algorithm>
    #include <stdexcept>
    #include <limits>

    namespace kapil {
    class string final {
    private:
    // default capacity of empty string.
    //Minimum default_capacity is 2.
    static constexpr size_t default_capacity_ = 16;
    // current capacity of the string container.
    size_t current_capacity_;
    //size of string.
    size_t sz_;
    // pointer to character
    std::unique_ptr<char> ptr_;
    public:
    string();
    string(const string&);
    string(string&&) noexcept;
    string(const char*);
    explicit string(char);
    ~string() noexcept;

    size_t capacity() const noexcept;
    size_t size() const noexcept;
    size_t length() const noexcept;
    void resize(size_t, char ch = '');
    void clear() noexcept;
    bool empty() const noexcept;
    char& at(size_t);
    const char& at(size_t) const;
    char& back();
    const char& back() const;
    char& front();
    const char& front() const;
    string& append(const string&);
    string& append(const char*);
    string& append(string&&);
    string& append(char);
    void push_back(char);
    string& assign(const string&);
    string& assign(const char*);
    string& assign(string&&);
    void swap(string&);
    const char* c_str() const noexcept;
    const char* data() const noexcept;



    string& operator = (const string&);
    string& operator = (string&&) noexcept;
    string& operator = (const char*);
    string& operator = (char);
    string& operator += (const string&);
    string& operator += (const char*);
    string& operator += (char);
    string& operator += (string&&);
    char& operator (size_t);
    const char& operator (size_t) const;

    friend std::ostream& operator << (std::ostream&, const string&);
    friend string operator + (const string&, const string&);
    friend string operator + (const string&, const char*);
    friend string operator + (const char*, const string&);
    friend string operator + (string&&, string&&);
    friend string operator + (string&&, const char*);
    friend string operator + (const char*, string&&);
    friend string operator + (const string&, string&&);
    friend string operator + (string&&, const string&);
    friend string operator + (const string&, char);
    friend string operator + (char, const string&);
    friend string operator + (string&&, char);
    friend string operator + (char, string&&);
    friend bool operator == (const string&, const string&) noexcept;
    friend bool operator == (const string&, const char*) noexcept;
    friend bool operator == (const char*, const string&) noexcept;
    friend bool operator == (const string&, char) noexcept;
    friend bool operator == (char, const string&) noexcept;
    friend bool operator == (const string&, string&&) noexcept;
    friend bool operator == (string&&, const string&) noexcept;
    friend bool operator == (string&&, string&&) noexcept;
    friend bool operator == (string&&, char) noexcept;
    friend bool operator == (char, string&&) noexcept;
    friend bool operator == (const char*, string&&) noexcept;
    friend bool operator == (string&&, const char*) noexcept;
    friend bool operator != (const string&, const string&) noexcept;
    friend bool operator != (const string&, const char*) noexcept;
    friend bool operator != (const char*, const string&) noexcept;
    friend bool operator != (const string&, char) noexcept;
    friend bool operator != (char, const string&) noexcept;
    friend bool operator != (const string&, string&&) noexcept;
    friend bool operator != (string&&, const string&) noexcept;
    friend bool operator != (string&&, string&&) noexcept;
    friend bool operator != (string&&, char) noexcept;
    friend bool operator != (char, string&&) noexcept;
    friend bool operator != (const char*, string&&) noexcept;
    friend bool operator != (string&&, const char*) noexcept;

    class iterator
    {
    private:
    string* str_;
    size_t index_;
    public:
    iterator(string* = nullptr, size_t = 0) noexcept;
    iterator(const iterator&) noexcept;
    iterator(iterator&&) noexcept;
    ~iterator() noexcept;

    iterator& operator = (const iterator&) noexcept;
    iterator& operator = (iterator&&) noexcept;
    bool operator != (const iterator&) const noexcept;
    bool operator == (const iterator&) const noexcept;
    iterator& operator ++ () noexcept;
    iterator& operator ++ (int) noexcept;
    iterator& operator -- () noexcept;
    iterator& operator -- (int) noexcept;
    char& operator * () const;
    };

    iterator begin();
    iterator end();

    class const_iterator
    {
    private:
    const string* str_;
    size_t index_;
    public:
    const_iterator(const string*, size_t) noexcept;
    const_iterator(const const_iterator&) noexcept;
    const_iterator(const_iterator&&) noexcept;
    ~const_iterator() noexcept;

    const_iterator& operator = (const const_iterator&) noexcept;
    const_iterator& operator = (const_iterator&&) noexcept;
    bool operator != (const const_iterator&) const noexcept;
    bool operator == (const const_iterator&) const noexcept;
    const_iterator& operator ++ () noexcept;
    const_iterator& operator ++ (int) noexcept;
    const_iterator& operator -- () noexcept;
    const_iterator& operator -- (int) noexcept;
    const char& operator * () const;
    };

    const_iterator cbegin();
    const_iterator cend();

    class reverse_iterator
    {
    private:
    string* str_;
    size_t index_;
    public:
    reverse_iterator(string* = nullptr, size_t = 0) noexcept;
    reverse_iterator(const reverse_iterator&) noexcept;
    reverse_iterator(reverse_iterator&&) noexcept;
    ~reverse_iterator() noexcept;

    reverse_iterator& operator = (const reverse_iterator&) noexcept;
    reverse_iterator& operator = (reverse_iterator&&) noexcept;
    bool operator != (const reverse_iterator&) const noexcept;
    bool operator == (const reverse_iterator&) const noexcept;
    reverse_iterator& operator ++ () noexcept;
    reverse_iterator& operator ++ (int) noexcept;
    reverse_iterator& operator -- () noexcept;
    reverse_iterator& operator -- (int) noexcept;
    char& operator * () const;
    };

    reverse_iterator rbegin();
    reverse_iterator rend();

    class reverse_const_iterator
    {
    private:
    const string* str_;
    size_t index_;
    public:
    reverse_const_iterator(const string*, size_t) noexcept;
    reverse_const_iterator(const reverse_const_iterator&) noexcept;
    reverse_const_iterator(reverse_const_iterator&&) noexcept;
    ~reverse_const_iterator() noexcept;

    reverse_const_iterator& operator = (const reverse_const_iterator&) noexcept;
    reverse_const_iterator& operator = (reverse_const_iterator&&) noexcept;
    bool operator != (const reverse_const_iterator&) const noexcept;
    bool operator == (const reverse_const_iterator&) const noexcept;
    reverse_const_iterator& operator ++ () noexcept;
    reverse_const_iterator& operator ++ (int) noexcept;
    reverse_const_iterator& operator -- () noexcept;
    reverse_const_iterator& operator -- (int) noexcept;
    const char& operator * () const;
    };

    reverse_const_iterator crbegin();
    reverse_const_iterator crend();
    };
    } //kapil

    #endif


    my_string.cpp



    #include "my_string.h"

    namespace kapil {
    /*
    For the given new_string_length, the appropriate capacity is the
    next power of 2 that is greater than new_string_length.
    */
    size_t get_appropriate_capacity(size_t new_string_length) {
    size_t appropriate_capacity = 16;
    if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
    appropriate_capacity = new_string_length;
    } else {
    appropriate_capacity = 16;
    if (appropriate_capacity <= new_string_length) {
    if (!(new_string_length & (new_string_length - 1))) {
    appropriate_capacity = new_string_length << 1;
    } else {
    while (appropriate_capacity < new_string_length) {
    appropriate_capacity <<= 1;
    }
    }
    }
    }
    return appropriate_capacity;
    }

    /**************************************** member functions *********************/

    string::string()
    : current_capacity_{ default_capacity_ } {
    sz_ = 0;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    ptr_.get()[0] = '';
    }

    string::string(const string& other) {
    current_capacity_ = other.current_capacity_;
    sz_ = other.sz_;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    std::strcpy(ptr_.get(), other.ptr_.get());
    }

    string::string(string&& rval) noexcept
    : current_capacity_{ rval.current_capacity_ },
    sz_{ rval.sz_ },
    ptr_{ std::move(rval.ptr_) } {
    }

    string::string(const char* c_string) {
    sz_ = std::strlen(c_string);
    current_capacity_ = get_appropriate_capacity(sz_);
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    std::strcpy(ptr_.get(), c_string);
    }

    string::string(char ch) {
    sz_ = 1;
    current_capacity_ = default_capacity_;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    ptr_.get()[0] = ch;
    ptr_.get()[1] = '';
    }

    string::~string() noexcept {
    current_capacity_ = 0;
    sz_ = 0;
    ptr_.reset(nullptr);
    };


    /**************************************** member functions *********************/

    size_t string::capacity() const noexcept {
    return current_capacity_;
    }

    size_t string::size() const noexcept {
    return sz_;
    }

    size_t string::length() const noexcept {
    return sz_;
    }

    void string::resize(size_t n, char ch) {
    if (n == sz_) {
    return;
    }

    size_t appropriate_capacity = get_appropriate_capacity(n);

    std::unique_ptr<char> temp;
    auto resized = bool{false};

    if (current_capacity_ != appropriate_capacity) {
    resized = true;
    current_capacity_ = appropriate_capacity;
    temp = std::make_unique<char>(current_capacity_ + 1);
    }

    if (n < sz_) {
    if (resized) {
    std::strncpy(temp.get(), ptr_.get(), n);
    temp.get()[n] = '';
    } else {
    ptr_.get()[n] = '';
    }
    } else if (n > sz_) {
    if (resized) {
    std::strncpy(temp.get(), ptr_.get(), sz_);
    std::fill(temp.get() + sz_, temp.get() + n, ch);
    temp.get()[n] = '';
    } else {
    std::fill(ptr_.get() + sz_, ptr_.get() + n, ch);
    ptr_.get()[n] = '';
    }
    }

    sz_ = n;
    if (resized) {
    ptr_ = std::move(temp);
    }
    }

    void string::clear() noexcept {
    current_capacity_ = default_capacity_;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    ptr_.get()[0] = '';
    sz_ = 0;
    }

    bool string::empty() const noexcept {
    return sz_ == 0;
    }

    char& string::at(size_t idx) {
    if (idx < 0 || idx >= sz_) {
    throw std::out_of_range{"out of range memory access"};
    }
    return (*this)[idx];
    }

    const char& string::at(size_t idx) const {
    if (idx < 0 || idx >= sz_) {
    throw std::out_of_range{"out of range memory access"};
    }
    return (*this)[idx];
    }

    char& string::back() {
    return (*this)[sz_ - 1];
    }

    const char& string::back() const {
    return (*this)[sz_ - 1];
    }

    char& string::front() {
    return (*this)[0];
    }

    const char& string::front() const {
    return (*this)[0];
    }

    string& string::append(const string& rhs) {
    (*this) += rhs;
    return *this;
    }

    string& string::append(const char* rhs) {
    (*this) += rhs;
    return *this;
    }

    string& string::append(string&& rhs) {
    (*this) += rhs;
    return *this;
    }

    string& string::append(char ch) {
    (*this) += ch;
    return *this;
    }

    void string::push_back(char ch) {
    (*this) += ch;
    return;
    }

    string& string::assign(const string& rhs) {
    (*this) = rhs;
    return *this;
    }

    string& string::assign(const char* rhs) {
    (*this) = rhs;
    return *this;
    }

    string& string::assign(string&& rhs) {
    (*this) = rhs;
    return *this;
    }

    void string::swap(string &str) {
    string temp{str};
    str = *this;
    *this = temp;
    }

    const char* string::c_str() const noexcept {
    return ptr_.get();
    }

    const char* string::data() const noexcept {
    return c_str();
    }



    /**************************************** member operator overloads*********************/

    string& string::operator = (const string& rhs) {
    if (this != &rhs) {
    if (current_capacity_ != rhs.current_capacity_) {
    current_capacity_ = rhs.current_capacity_;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    }
    sz_ = rhs.sz_;
    std::strcpy(ptr_.get(), rhs.c_str());
    }
    return *this;
    }

    string& string::operator = (string&& rval) noexcept {
    current_capacity_ = rval.current_capacity_;
    sz_ = rval.sz_;
    ptr_ = std::move(rval.ptr_);
    return *this;
    }

    string& string::operator = (const char* c_string) {
    sz_ = std::strlen(c_string);
    auto appropriate_capacity = get_appropriate_capacity(sz_);
    if (current_capacity_ != appropriate_capacity) {
    current_capacity_ = appropriate_capacity;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    }
    std::strcpy(ptr_.get(), c_string);
    return *this;
    }

    string& string::operator = (char ch) {
    current_capacity_ = default_capacity_;
    sz_ = 1;
    ptr_ = std::make_unique<char>(current_capacity_ + 1);
    ptr_.get()[0] = ch;
    ptr_.get()[1] = '';
    return *this;
    }

    string& string::operator += (const string& rhs) {
    std::unique_ptr<char> temp;
    auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs.sz_);

    if (current_capacity_ != appropriate_capacity) {
    current_capacity_ = appropriate_capacity;
    temp = std::make_unique<char>(current_capacity_ + 1);
    std::strcpy(temp.get(), ptr_.get());
    ptr_ = std::move(temp);
    }

    std::strcpy(ptr_.get() + sz_, rhs.c_str());
    sz_ += rhs.sz_;

    return *this;
    }

    string& string::operator += (const char* rhs) {
    std::unique_ptr<char> temp;
    auto rhs_sz = std::strlen(rhs);
    auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs_sz);

    if (current_capacity_ != appropriate_capacity) {
    current_capacity_ = appropriate_capacity;
    temp = std::make_unique<char>(current_capacity_ + 1);
    std::strcpy(temp.get(), ptr_.get());
    ptr_ = std::move(temp);
    }

    std::strcpy(ptr_.get() + sz_, rhs);
    sz_ += rhs_sz;

    return *this;
    }

    string& string::operator += (char ch) {
    auto appropriate_capacity = get_appropriate_capacity(sz_ + 1);
    std::unique_ptr<char> temp;

    if (current_capacity_ != appropriate_capacity) {
    current_capacity_ = appropriate_capacity;
    temp = std::make_unique<char>(current_capacity_ + 1);
    strcpy(temp.get(), ptr_.get());
    ptr_ = std::move(temp);
    }
    ptr_.get()[sz_] = ch;
    ptr_.get()[sz_ + 1] = '';
    sz_ += 1;

    return *this;
    }


    string& string::operator += (string&& rval) {
    std::unique_ptr<char> temp;
    auto appropriate_capacity = get_appropriate_capacity(sz_ + rval.sz_);

    if (current_capacity_ != appropriate_capacity) {
    current_capacity_ = appropriate_capacity;
    temp = std::make_unique<char>(current_capacity_ + 1);
    std::strcpy(temp.get(), ptr_.get());
    ptr_ = std::move(temp);
    }

    std::strcpy(ptr_.get() + sz_, rval.c_str());
    sz_ += rval.sz_;

    return *this;
    }

    char& string::operator (size_t idx) {
    return ptr_.get()[idx];
    }

    const char& string::operator (size_t idx) const {
    return ptr_.get()[idx];
    }



    /**************************************** friend operator overloads *********************/

    std::ostream& operator << (std::ostream& out, const string& str) {
    if (str.size() > 0) {
    out.write(str.c_str(), str.size());
    }
    return out;
    }

    string operator + (const string& lhs, const string& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (const string& lhs, const char* rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (const char* lhs, const string& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (string&& lhs, string&& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (string&& lhs, const char* rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (const char* lhs, string&& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (const string& lhs, string&& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (string&& lhs, const string& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (const string& lhs, char rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (char lhs, const string& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (string&& lhs, char rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    string operator + (char lhs, string&& rhs) {
    string temp{lhs};
    temp += rhs;
    return temp;
    }

    bool operator == (const string& lhs, const string& rhs) noexcept {
    return (lhs.sz_ == rhs.sz_) &&
    ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
    }

    bool operator == (const string& lhs, const char* rhs) noexcept {
    return (lhs.sz_ == std::strlen(rhs)) &&
    ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
    }

    bool operator == (const char* lhs, const string& rhs) noexcept {
    return (strlen(lhs) == rhs.sz_) &&
    ((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
    }

    bool operator == (const string& lhs, char rhs) noexcept {
    return (lhs.sz_ == 1) &&
    (lhs.ptr_.get()[0] == rhs);
    }

    bool operator == (char lhs, const string& rhs) noexcept {
    return (rhs.sz_ == 1) &&
    (lhs == rhs.ptr_.get()[0]);
    }

    bool operator == (const string& lhs, string&& rhs) noexcept {
    return (lhs.sz_ == rhs.sz_) &&
    ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
    }

    bool operator == (string&& lhs, const string& rhs) noexcept {
    return (lhs.sz_ == rhs.sz_) &&
    ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
    }

    bool operator == (string&& lhs, string&& rhs) noexcept {
    return (lhs.sz_ == rhs.sz_) &&
    ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
    }

    bool operator == (string&& lhs, char rhs) noexcept {
    return (lhs.sz_ == 1) &&
    (lhs.ptr_.get()[0] == rhs);
    }

    bool operator == (char lhs, string&& rhs) noexcept {
    return (rhs.sz_ == 1) &&
    (rhs.ptr_.get()[0] == lhs);
    }

    bool operator == (string&& lhs, const char* rhs) noexcept {
    return (lhs.sz_ == std::strlen(rhs)) &&
    ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
    }

    bool operator == (const char* lhs, string && rhs) noexcept {
    return (std::strlen(lhs) == rhs.sz_) &&
    ((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
    }

    bool operator != (const string& lhs, const string& rhs) noexcept {
    return !(lhs == rhs);
    }

    bool operator != (const string& lhs, const char* rhs) noexcept {
    return !(lhs == rhs);
    }

    bool operator != (const char* lhs, const string& rhs) noexcept {
    return !(lhs == rhs);
    }

    bool operator != (const string& lhs, char rhs) noexcept {
    return !(lhs == rhs);
    }

    bool operator != (char lhs, const string& rhs) noexcept {
    return !(lhs == rhs);
    }

    bool operator != (const string& lhs, string&& rhs) noexcept {
    return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
    }

    bool operator != (string&& lhs, const string& rhs) noexcept {
    return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
    }

    bool operator != (string&& lhs, string&& rhs) noexcept {
    return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
    }

    bool operator != (string&& lhs, char rhs) noexcept {
    return (lhs.sz_ != 1) || (lhs.ptr_.get()[0] != rhs);
    }

    bool operator != (char lhs, string&& rhs) noexcept {
    return (rhs.sz_ != 1) || (rhs.ptr_.get()[0] != lhs);
    }

    bool operator != (string&& lhs, const char* rhs) noexcept {
    return (lhs.sz_ != std::strlen(rhs)) || (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) != 0);
    }

    bool operator != (const char* lhs, string && rhs) noexcept {
    return (std::strlen(lhs) != rhs.sz_) || (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) != 0);
    }



    /**************************************** iterator related implementations *********************/

    using iterator = string::iterator;

    iterator::iterator(string *str, size_t index) noexcept
    : str_{str}, index_{index} {
    }

    iterator::iterator(const iterator& itr) noexcept
    : str_{itr.str_}, index_{itr.index_} {
    }

    iterator::iterator(iterator&& rval) noexcept
    : str_{rval.str_}, index_{rval.index_} {
    }

    iterator::~iterator() noexcept {
    str_ = nullptr;
    index_ = 0;
    }



    iterator& iterator::operator = (const iterator& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    iterator& iterator::operator = (iterator&& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    bool iterator::operator != (const iterator& rhs) const noexcept {
    return (str_ != rhs.str_) || (index_ != rhs.index_);
    }

    bool iterator::operator == (const iterator& rhs) const noexcept {
    return (str_ == rhs.str_) && (index_ == rhs.index_);
    }

    iterator& iterator::operator ++ () noexcept {
    ++index_;
    return *this;
    }

    iterator& iterator::operator ++ (int dummy) noexcept {
    ++(*this);
    return *this;
    }

    iterator& iterator::operator -- () noexcept {
    --index_;
    return *this;
    }

    iterator& iterator::operator -- (int dummy) noexcept {
    --(*this);
    return *this;
    }

    char& iterator::operator * () const {
    return (*str_)[index_];
    }

    iterator string::begin() {
    return iterator(this);
    }

    iterator string::end() {
    return iterator(this, sz_);
    }



    using const_iterator = string::const_iterator;

    const_iterator::const_iterator(const string* str, size_t index) noexcept
    : str_{str}, index_{index} {
    }

    const_iterator::const_iterator(const const_iterator& itr) noexcept
    : str_{itr.str_}, index_{itr.index_} {
    }

    const_iterator::const_iterator(const_iterator&& rval) noexcept
    : str_{rval.str_}, index_{rval.index_} {
    }

    const_iterator::~const_iterator() noexcept {
    str_ = nullptr;
    index_ = 0;
    }



    const_iterator& const_iterator::operator = (const const_iterator& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    const_iterator& const_iterator::operator = (const_iterator&& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    bool const_iterator::operator != (const const_iterator& rhs) const noexcept {
    return (str_ != rhs.str_) || (index_ != rhs.index_);
    }

    bool const_iterator::operator == (const const_iterator& rhs) const noexcept {
    return (str_ == rhs.str_) && (index_ == rhs.index_);
    }

    const_iterator& const_iterator::operator ++ () noexcept {
    ++index_;
    return *this;
    }

    const_iterator& const_iterator::operator ++ (int dummy) noexcept {
    ++(*this);
    return *this;
    }

    const_iterator& const_iterator::operator -- () noexcept {
    --index_;
    return *this;
    }

    const_iterator& const_iterator::operator -- (int dummy) noexcept {
    --(*this);
    return *this;
    }

    const char& const_iterator::operator * () const {
    return (*str_)[index_];
    }

    const_iterator string::cbegin() {
    return const_iterator(this, 0);
    }

    const_iterator string::cend() {
    return const_iterator(this, sz_);
    }



    using reverse_iterator = string::reverse_iterator;

    reverse_iterator::reverse_iterator(string *str, size_t index) noexcept
    : str_{str}, index_{index} {
    }

    reverse_iterator::reverse_iterator(const reverse_iterator& itr) noexcept
    : str_{itr.str_}, index_{itr.index_} {
    }

    reverse_iterator::reverse_iterator(reverse_iterator&& rval) noexcept
    : str_{rval.str_}, index_{rval.index_} {
    }

    reverse_iterator::~reverse_iterator() noexcept {
    str_ = nullptr;
    index_ = 0;
    }



    reverse_iterator& reverse_iterator::operator = (const reverse_iterator& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    reverse_iterator& reverse_iterator::operator = (reverse_iterator&& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    bool reverse_iterator::operator != (const reverse_iterator& rhs) const noexcept {
    return (str_ != rhs.str_) || (index_ != rhs.index_);
    }

    bool reverse_iterator::operator == (const reverse_iterator& rhs) const noexcept {
    return (str_ == rhs.str_) && (index_ == rhs.index_);
    }

    reverse_iterator& reverse_iterator::operator ++ () noexcept {
    --index_;
    return *this;
    }

    reverse_iterator& reverse_iterator::operator ++ (int dummy) noexcept {
    ++(*this);
    return *this;
    }

    reverse_iterator& reverse_iterator::operator -- () noexcept {
    ++index_;
    return *this;
    }

    reverse_iterator& reverse_iterator::operator -- (int dummy) noexcept {
    --(*this);
    return *this;
    }

    char& reverse_iterator::operator * () const {
    return (*str_)[index_];
    }

    reverse_iterator string::rbegin() {
    return reverse_iterator(this, sz_ - 1);
    }

    reverse_iterator string::rend() {
    return reverse_iterator(this, -1);
    }



    using reverse_const_iterator = string::reverse_const_iterator;

    reverse_const_iterator::reverse_const_iterator(const string* str, size_t index) noexcept
    : str_{str}, index_{index} {
    }

    reverse_const_iterator::reverse_const_iterator(const reverse_const_iterator& itr) noexcept
    : str_{itr.str_}, index_{itr.index_} {
    }

    reverse_const_iterator::reverse_const_iterator(reverse_const_iterator&& rval) noexcept
    : str_{rval.str_}, index_{rval.index_} {
    }

    reverse_const_iterator::~reverse_const_iterator() noexcept {
    str_ = nullptr;
    index_ = 0;
    }



    reverse_const_iterator& reverse_const_iterator::operator = (const reverse_const_iterator& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    reverse_const_iterator& reverse_const_iterator::operator = (reverse_const_iterator&& rhs) noexcept {
    str_ = rhs.str_;
    index_ = rhs.index_;
    return *this;
    }

    bool reverse_const_iterator::operator != (const reverse_const_iterator& rhs) const noexcept {
    return (str_ != rhs.str_) || (index_ != rhs.index_);
    }

    bool reverse_const_iterator::operator == (const reverse_const_iterator& rhs) const noexcept {
    return (str_ == rhs.str_) && (index_ == rhs.index_);
    }

    reverse_const_iterator& reverse_const_iterator::operator ++ () noexcept {
    --index_;
    return *this;
    }

    reverse_const_iterator& reverse_const_iterator::operator ++ (int dummy) noexcept {
    ++(*this);
    return *this;
    }

    reverse_const_iterator& reverse_const_iterator::operator -- () noexcept {
    ++index_;
    return *this;
    }

    reverse_const_iterator& reverse_const_iterator::operator -- (int dummy) noexcept {
    --(*this);
    return *this;
    }

    const char& reverse_const_iterator::operator * () const {
    return (*str_)[index_];
    }

    reverse_const_iterator string::crbegin() {
    return reverse_const_iterator(this, sz_ - 1);
    }

    reverse_const_iterator string::crend() {
    return reverse_const_iterator(this, -1);
    }
    } //kapil


    Here is a test file which shows the usage:



    test_string.cpp



    #include "my_string.h"

    using namespace kapil;

    int main() {
    string x{"kapil"};
    std::cout << " size : " << x.size() << " length : " << x.length() << " is empty : " << x.empty() << " at 2 : " << x.at(2) << " back : " <<
    x.back() << " c_str : " << x.c_str() << " data : " << x.data() << std::endl;
    x.clear();
    x = "dev";
    string y{" singh"};
    x.append(y);
    x.append(" is");
    y.assign(" assigned");
    x += " operator +";
    x += y;

    std::cout << " x : " << x << " x cap : " << x.capacity() << "n y : " << y << " y cap : " << y.capacity() << std::endl;
    string added = "i am binary + " + y + string{" ravl add "} + 'x';
    std::cout << " added : " << added << " added cap : " << added.capacity() << std::endl;
    added = "kapil";
    added.resize(10, 'k');
    std::cout << " added resize 10 : " << added << " added cap : " << added.capacity() << std::endl;
    added.resize(78, 'l');
    std::cout << " added resize 78 : " << added << " added cap : " << added.capacity() << std::endl;
    string s1 = "kapil";
    s1.swap(added);
    std::cout << " added : " << added << " s1 : " << s1 << std::endl;

    for (auto it : s1) {
    std::cout << it << " ";
    }

    std::cout << "n";
    return 0;
    }









    share|improve this question









    New contributor




    kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.























      3












      3








      3


      1





      This is my implementation of a string class similar to std::string and supports the following:




      1. range-for loops

      2. iterators

      3. basic utility functions

      4. exponential capacity increase

      5. operator overloads


      I am looking for specific reviews on implementation of iterators, exponential capacity increase, usage of noexcept and implementations of some functions like resize(), operator+=, operator=, and operator+.



      mystring.h



      #ifndef MY_STRING_H_
      #define MY_STRING_H_

      #include <cstring>
      #include <iostream>
      #include <memory>
      #include <algorithm>
      #include <stdexcept>
      #include <limits>

      namespace kapil {
      class string final {
      private:
      // default capacity of empty string.
      //Minimum default_capacity is 2.
      static constexpr size_t default_capacity_ = 16;
      // current capacity of the string container.
      size_t current_capacity_;
      //size of string.
      size_t sz_;
      // pointer to character
      std::unique_ptr<char> ptr_;
      public:
      string();
      string(const string&);
      string(string&&) noexcept;
      string(const char*);
      explicit string(char);
      ~string() noexcept;

      size_t capacity() const noexcept;
      size_t size() const noexcept;
      size_t length() const noexcept;
      void resize(size_t, char ch = '');
      void clear() noexcept;
      bool empty() const noexcept;
      char& at(size_t);
      const char& at(size_t) const;
      char& back();
      const char& back() const;
      char& front();
      const char& front() const;
      string& append(const string&);
      string& append(const char*);
      string& append(string&&);
      string& append(char);
      void push_back(char);
      string& assign(const string&);
      string& assign(const char*);
      string& assign(string&&);
      void swap(string&);
      const char* c_str() const noexcept;
      const char* data() const noexcept;



      string& operator = (const string&);
      string& operator = (string&&) noexcept;
      string& operator = (const char*);
      string& operator = (char);
      string& operator += (const string&);
      string& operator += (const char*);
      string& operator += (char);
      string& operator += (string&&);
      char& operator (size_t);
      const char& operator (size_t) const;

      friend std::ostream& operator << (std::ostream&, const string&);
      friend string operator + (const string&, const string&);
      friend string operator + (const string&, const char*);
      friend string operator + (const char*, const string&);
      friend string operator + (string&&, string&&);
      friend string operator + (string&&, const char*);
      friend string operator + (const char*, string&&);
      friend string operator + (const string&, string&&);
      friend string operator + (string&&, const string&);
      friend string operator + (const string&, char);
      friend string operator + (char, const string&);
      friend string operator + (string&&, char);
      friend string operator + (char, string&&);
      friend bool operator == (const string&, const string&) noexcept;
      friend bool operator == (const string&, const char*) noexcept;
      friend bool operator == (const char*, const string&) noexcept;
      friend bool operator == (const string&, char) noexcept;
      friend bool operator == (char, const string&) noexcept;
      friend bool operator == (const string&, string&&) noexcept;
      friend bool operator == (string&&, const string&) noexcept;
      friend bool operator == (string&&, string&&) noexcept;
      friend bool operator == (string&&, char) noexcept;
      friend bool operator == (char, string&&) noexcept;
      friend bool operator == (const char*, string&&) noexcept;
      friend bool operator == (string&&, const char*) noexcept;
      friend bool operator != (const string&, const string&) noexcept;
      friend bool operator != (const string&, const char*) noexcept;
      friend bool operator != (const char*, const string&) noexcept;
      friend bool operator != (const string&, char) noexcept;
      friend bool operator != (char, const string&) noexcept;
      friend bool operator != (const string&, string&&) noexcept;
      friend bool operator != (string&&, const string&) noexcept;
      friend bool operator != (string&&, string&&) noexcept;
      friend bool operator != (string&&, char) noexcept;
      friend bool operator != (char, string&&) noexcept;
      friend bool operator != (const char*, string&&) noexcept;
      friend bool operator != (string&&, const char*) noexcept;

      class iterator
      {
      private:
      string* str_;
      size_t index_;
      public:
      iterator(string* = nullptr, size_t = 0) noexcept;
      iterator(const iterator&) noexcept;
      iterator(iterator&&) noexcept;
      ~iterator() noexcept;

      iterator& operator = (const iterator&) noexcept;
      iterator& operator = (iterator&&) noexcept;
      bool operator != (const iterator&) const noexcept;
      bool operator == (const iterator&) const noexcept;
      iterator& operator ++ () noexcept;
      iterator& operator ++ (int) noexcept;
      iterator& operator -- () noexcept;
      iterator& operator -- (int) noexcept;
      char& operator * () const;
      };

      iterator begin();
      iterator end();

      class const_iterator
      {
      private:
      const string* str_;
      size_t index_;
      public:
      const_iterator(const string*, size_t) noexcept;
      const_iterator(const const_iterator&) noexcept;
      const_iterator(const_iterator&&) noexcept;
      ~const_iterator() noexcept;

      const_iterator& operator = (const const_iterator&) noexcept;
      const_iterator& operator = (const_iterator&&) noexcept;
      bool operator != (const const_iterator&) const noexcept;
      bool operator == (const const_iterator&) const noexcept;
      const_iterator& operator ++ () noexcept;
      const_iterator& operator ++ (int) noexcept;
      const_iterator& operator -- () noexcept;
      const_iterator& operator -- (int) noexcept;
      const char& operator * () const;
      };

      const_iterator cbegin();
      const_iterator cend();

      class reverse_iterator
      {
      private:
      string* str_;
      size_t index_;
      public:
      reverse_iterator(string* = nullptr, size_t = 0) noexcept;
      reverse_iterator(const reverse_iterator&) noexcept;
      reverse_iterator(reverse_iterator&&) noexcept;
      ~reverse_iterator() noexcept;

      reverse_iterator& operator = (const reverse_iterator&) noexcept;
      reverse_iterator& operator = (reverse_iterator&&) noexcept;
      bool operator != (const reverse_iterator&) const noexcept;
      bool operator == (const reverse_iterator&) const noexcept;
      reverse_iterator& operator ++ () noexcept;
      reverse_iterator& operator ++ (int) noexcept;
      reverse_iterator& operator -- () noexcept;
      reverse_iterator& operator -- (int) noexcept;
      char& operator * () const;
      };

      reverse_iterator rbegin();
      reverse_iterator rend();

      class reverse_const_iterator
      {
      private:
      const string* str_;
      size_t index_;
      public:
      reverse_const_iterator(const string*, size_t) noexcept;
      reverse_const_iterator(const reverse_const_iterator&) noexcept;
      reverse_const_iterator(reverse_const_iterator&&) noexcept;
      ~reverse_const_iterator() noexcept;

      reverse_const_iterator& operator = (const reverse_const_iterator&) noexcept;
      reverse_const_iterator& operator = (reverse_const_iterator&&) noexcept;
      bool operator != (const reverse_const_iterator&) const noexcept;
      bool operator == (const reverse_const_iterator&) const noexcept;
      reverse_const_iterator& operator ++ () noexcept;
      reverse_const_iterator& operator ++ (int) noexcept;
      reverse_const_iterator& operator -- () noexcept;
      reverse_const_iterator& operator -- (int) noexcept;
      const char& operator * () const;
      };

      reverse_const_iterator crbegin();
      reverse_const_iterator crend();
      };
      } //kapil

      #endif


      my_string.cpp



      #include "my_string.h"

      namespace kapil {
      /*
      For the given new_string_length, the appropriate capacity is the
      next power of 2 that is greater than new_string_length.
      */
      size_t get_appropriate_capacity(size_t new_string_length) {
      size_t appropriate_capacity = 16;
      if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
      appropriate_capacity = new_string_length;
      } else {
      appropriate_capacity = 16;
      if (appropriate_capacity <= new_string_length) {
      if (!(new_string_length & (new_string_length - 1))) {
      appropriate_capacity = new_string_length << 1;
      } else {
      while (appropriate_capacity < new_string_length) {
      appropriate_capacity <<= 1;
      }
      }
      }
      }
      return appropriate_capacity;
      }

      /**************************************** member functions *********************/

      string::string()
      : current_capacity_{ default_capacity_ } {
      sz_ = 0;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = '';
      }

      string::string(const string& other) {
      current_capacity_ = other.current_capacity_;
      sz_ = other.sz_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(ptr_.get(), other.ptr_.get());
      }

      string::string(string&& rval) noexcept
      : current_capacity_{ rval.current_capacity_ },
      sz_{ rval.sz_ },
      ptr_{ std::move(rval.ptr_) } {
      }

      string::string(const char* c_string) {
      sz_ = std::strlen(c_string);
      current_capacity_ = get_appropriate_capacity(sz_);
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(ptr_.get(), c_string);
      }

      string::string(char ch) {
      sz_ = 1;
      current_capacity_ = default_capacity_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = ch;
      ptr_.get()[1] = '';
      }

      string::~string() noexcept {
      current_capacity_ = 0;
      sz_ = 0;
      ptr_.reset(nullptr);
      };


      /**************************************** member functions *********************/

      size_t string::capacity() const noexcept {
      return current_capacity_;
      }

      size_t string::size() const noexcept {
      return sz_;
      }

      size_t string::length() const noexcept {
      return sz_;
      }

      void string::resize(size_t n, char ch) {
      if (n == sz_) {
      return;
      }

      size_t appropriate_capacity = get_appropriate_capacity(n);

      std::unique_ptr<char> temp;
      auto resized = bool{false};

      if (current_capacity_ != appropriate_capacity) {
      resized = true;
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      }

      if (n < sz_) {
      if (resized) {
      std::strncpy(temp.get(), ptr_.get(), n);
      temp.get()[n] = '';
      } else {
      ptr_.get()[n] = '';
      }
      } else if (n > sz_) {
      if (resized) {
      std::strncpy(temp.get(), ptr_.get(), sz_);
      std::fill(temp.get() + sz_, temp.get() + n, ch);
      temp.get()[n] = '';
      } else {
      std::fill(ptr_.get() + sz_, ptr_.get() + n, ch);
      ptr_.get()[n] = '';
      }
      }

      sz_ = n;
      if (resized) {
      ptr_ = std::move(temp);
      }
      }

      void string::clear() noexcept {
      current_capacity_ = default_capacity_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = '';
      sz_ = 0;
      }

      bool string::empty() const noexcept {
      return sz_ == 0;
      }

      char& string::at(size_t idx) {
      if (idx < 0 || idx >= sz_) {
      throw std::out_of_range{"out of range memory access"};
      }
      return (*this)[idx];
      }

      const char& string::at(size_t idx) const {
      if (idx < 0 || idx >= sz_) {
      throw std::out_of_range{"out of range memory access"};
      }
      return (*this)[idx];
      }

      char& string::back() {
      return (*this)[sz_ - 1];
      }

      const char& string::back() const {
      return (*this)[sz_ - 1];
      }

      char& string::front() {
      return (*this)[0];
      }

      const char& string::front() const {
      return (*this)[0];
      }

      string& string::append(const string& rhs) {
      (*this) += rhs;
      return *this;
      }

      string& string::append(const char* rhs) {
      (*this) += rhs;
      return *this;
      }

      string& string::append(string&& rhs) {
      (*this) += rhs;
      return *this;
      }

      string& string::append(char ch) {
      (*this) += ch;
      return *this;
      }

      void string::push_back(char ch) {
      (*this) += ch;
      return;
      }

      string& string::assign(const string& rhs) {
      (*this) = rhs;
      return *this;
      }

      string& string::assign(const char* rhs) {
      (*this) = rhs;
      return *this;
      }

      string& string::assign(string&& rhs) {
      (*this) = rhs;
      return *this;
      }

      void string::swap(string &str) {
      string temp{str};
      str = *this;
      *this = temp;
      }

      const char* string::c_str() const noexcept {
      return ptr_.get();
      }

      const char* string::data() const noexcept {
      return c_str();
      }



      /**************************************** member operator overloads*********************/

      string& string::operator = (const string& rhs) {
      if (this != &rhs) {
      if (current_capacity_ != rhs.current_capacity_) {
      current_capacity_ = rhs.current_capacity_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      }
      sz_ = rhs.sz_;
      std::strcpy(ptr_.get(), rhs.c_str());
      }
      return *this;
      }

      string& string::operator = (string&& rval) noexcept {
      current_capacity_ = rval.current_capacity_;
      sz_ = rval.sz_;
      ptr_ = std::move(rval.ptr_);
      return *this;
      }

      string& string::operator = (const char* c_string) {
      sz_ = std::strlen(c_string);
      auto appropriate_capacity = get_appropriate_capacity(sz_);
      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      }
      std::strcpy(ptr_.get(), c_string);
      return *this;
      }

      string& string::operator = (char ch) {
      current_capacity_ = default_capacity_;
      sz_ = 1;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = ch;
      ptr_.get()[1] = '';
      return *this;
      }

      string& string::operator += (const string& rhs) {
      std::unique_ptr<char> temp;
      auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs.sz_);

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }

      std::strcpy(ptr_.get() + sz_, rhs.c_str());
      sz_ += rhs.sz_;

      return *this;
      }

      string& string::operator += (const char* rhs) {
      std::unique_ptr<char> temp;
      auto rhs_sz = std::strlen(rhs);
      auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs_sz);

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }

      std::strcpy(ptr_.get() + sz_, rhs);
      sz_ += rhs_sz;

      return *this;
      }

      string& string::operator += (char ch) {
      auto appropriate_capacity = get_appropriate_capacity(sz_ + 1);
      std::unique_ptr<char> temp;

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }
      ptr_.get()[sz_] = ch;
      ptr_.get()[sz_ + 1] = '';
      sz_ += 1;

      return *this;
      }


      string& string::operator += (string&& rval) {
      std::unique_ptr<char> temp;
      auto appropriate_capacity = get_appropriate_capacity(sz_ + rval.sz_);

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }

      std::strcpy(ptr_.get() + sz_, rval.c_str());
      sz_ += rval.sz_;

      return *this;
      }

      char& string::operator (size_t idx) {
      return ptr_.get()[idx];
      }

      const char& string::operator (size_t idx) const {
      return ptr_.get()[idx];
      }



      /**************************************** friend operator overloads *********************/

      std::ostream& operator << (std::ostream& out, const string& str) {
      if (str.size() > 0) {
      out.write(str.c_str(), str.size());
      }
      return out;
      }

      string operator + (const string& lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const string& lhs, const char* rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const char* lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, const char* rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const char* lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const string& lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const string& lhs, char rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (char lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, char rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (char lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      bool operator == (const string& lhs, const string& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (const string& lhs, const char* rhs) noexcept {
      return (lhs.sz_ == std::strlen(rhs)) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
      }

      bool operator == (const char* lhs, const string& rhs) noexcept {
      return (strlen(lhs) == rhs.sz_) &&
      ((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
      }

      bool operator == (const string& lhs, char rhs) noexcept {
      return (lhs.sz_ == 1) &&
      (lhs.ptr_.get()[0] == rhs);
      }

      bool operator == (char lhs, const string& rhs) noexcept {
      return (rhs.sz_ == 1) &&
      (lhs == rhs.ptr_.get()[0]);
      }

      bool operator == (const string& lhs, string&& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (string&& lhs, const string& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (string&& lhs, string&& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (string&& lhs, char rhs) noexcept {
      return (lhs.sz_ == 1) &&
      (lhs.ptr_.get()[0] == rhs);
      }

      bool operator == (char lhs, string&& rhs) noexcept {
      return (rhs.sz_ == 1) &&
      (rhs.ptr_.get()[0] == lhs);
      }

      bool operator == (string&& lhs, const char* rhs) noexcept {
      return (lhs.sz_ == std::strlen(rhs)) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
      }

      bool operator == (const char* lhs, string && rhs) noexcept {
      return (std::strlen(lhs) == rhs.sz_) &&
      ((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
      }

      bool operator != (const string& lhs, const string& rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const string& lhs, const char* rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const char* lhs, const string& rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const string& lhs, char rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (char lhs, const string& rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const string& lhs, string&& rhs) noexcept {
      return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
      }

      bool operator != (string&& lhs, const string& rhs) noexcept {
      return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
      }

      bool operator != (string&& lhs, string&& rhs) noexcept {
      return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
      }

      bool operator != (string&& lhs, char rhs) noexcept {
      return (lhs.sz_ != 1) || (lhs.ptr_.get()[0] != rhs);
      }

      bool operator != (char lhs, string&& rhs) noexcept {
      return (rhs.sz_ != 1) || (rhs.ptr_.get()[0] != lhs);
      }

      bool operator != (string&& lhs, const char* rhs) noexcept {
      return (lhs.sz_ != std::strlen(rhs)) || (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) != 0);
      }

      bool operator != (const char* lhs, string && rhs) noexcept {
      return (std::strlen(lhs) != rhs.sz_) || (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) != 0);
      }



      /**************************************** iterator related implementations *********************/

      using iterator = string::iterator;

      iterator::iterator(string *str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      iterator::iterator(const iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      iterator::iterator(iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      iterator::~iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      iterator& iterator::operator = (const iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      iterator& iterator::operator = (iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool iterator::operator != (const iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool iterator::operator == (const iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      iterator& iterator::operator ++ () noexcept {
      ++index_;
      return *this;
      }

      iterator& iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      iterator& iterator::operator -- () noexcept {
      --index_;
      return *this;
      }

      iterator& iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      char& iterator::operator * () const {
      return (*str_)[index_];
      }

      iterator string::begin() {
      return iterator(this);
      }

      iterator string::end() {
      return iterator(this, sz_);
      }



      using const_iterator = string::const_iterator;

      const_iterator::const_iterator(const string* str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      const_iterator::const_iterator(const const_iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      const_iterator::const_iterator(const_iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      const_iterator::~const_iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      const_iterator& const_iterator::operator = (const const_iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      const_iterator& const_iterator::operator = (const_iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool const_iterator::operator != (const const_iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool const_iterator::operator == (const const_iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      const_iterator& const_iterator::operator ++ () noexcept {
      ++index_;
      return *this;
      }

      const_iterator& const_iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      const_iterator& const_iterator::operator -- () noexcept {
      --index_;
      return *this;
      }

      const_iterator& const_iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      const char& const_iterator::operator * () const {
      return (*str_)[index_];
      }

      const_iterator string::cbegin() {
      return const_iterator(this, 0);
      }

      const_iterator string::cend() {
      return const_iterator(this, sz_);
      }



      using reverse_iterator = string::reverse_iterator;

      reverse_iterator::reverse_iterator(string *str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      reverse_iterator::reverse_iterator(const reverse_iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      reverse_iterator::reverse_iterator(reverse_iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      reverse_iterator::~reverse_iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      reverse_iterator& reverse_iterator::operator = (const reverse_iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      reverse_iterator& reverse_iterator::operator = (reverse_iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool reverse_iterator::operator != (const reverse_iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool reverse_iterator::operator == (const reverse_iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      reverse_iterator& reverse_iterator::operator ++ () noexcept {
      --index_;
      return *this;
      }

      reverse_iterator& reverse_iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      reverse_iterator& reverse_iterator::operator -- () noexcept {
      ++index_;
      return *this;
      }

      reverse_iterator& reverse_iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      char& reverse_iterator::operator * () const {
      return (*str_)[index_];
      }

      reverse_iterator string::rbegin() {
      return reverse_iterator(this, sz_ - 1);
      }

      reverse_iterator string::rend() {
      return reverse_iterator(this, -1);
      }



      using reverse_const_iterator = string::reverse_const_iterator;

      reverse_const_iterator::reverse_const_iterator(const string* str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      reverse_const_iterator::reverse_const_iterator(const reverse_const_iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      reverse_const_iterator::reverse_const_iterator(reverse_const_iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      reverse_const_iterator::~reverse_const_iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      reverse_const_iterator& reverse_const_iterator::operator = (const reverse_const_iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator = (reverse_const_iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool reverse_const_iterator::operator != (const reverse_const_iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool reverse_const_iterator::operator == (const reverse_const_iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      reverse_const_iterator& reverse_const_iterator::operator ++ () noexcept {
      --index_;
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator -- () noexcept {
      ++index_;
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      const char& reverse_const_iterator::operator * () const {
      return (*str_)[index_];
      }

      reverse_const_iterator string::crbegin() {
      return reverse_const_iterator(this, sz_ - 1);
      }

      reverse_const_iterator string::crend() {
      return reverse_const_iterator(this, -1);
      }
      } //kapil


      Here is a test file which shows the usage:



      test_string.cpp



      #include "my_string.h"

      using namespace kapil;

      int main() {
      string x{"kapil"};
      std::cout << " size : " << x.size() << " length : " << x.length() << " is empty : " << x.empty() << " at 2 : " << x.at(2) << " back : " <<
      x.back() << " c_str : " << x.c_str() << " data : " << x.data() << std::endl;
      x.clear();
      x = "dev";
      string y{" singh"};
      x.append(y);
      x.append(" is");
      y.assign(" assigned");
      x += " operator +";
      x += y;

      std::cout << " x : " << x << " x cap : " << x.capacity() << "n y : " << y << " y cap : " << y.capacity() << std::endl;
      string added = "i am binary + " + y + string{" ravl add "} + 'x';
      std::cout << " added : " << added << " added cap : " << added.capacity() << std::endl;
      added = "kapil";
      added.resize(10, 'k');
      std::cout << " added resize 10 : " << added << " added cap : " << added.capacity() << std::endl;
      added.resize(78, 'l');
      std::cout << " added resize 78 : " << added << " added cap : " << added.capacity() << std::endl;
      string s1 = "kapil";
      s1.swap(added);
      std::cout << " added : " << added << " s1 : " << s1 << std::endl;

      for (auto it : s1) {
      std::cout << it << " ";
      }

      std::cout << "n";
      return 0;
      }









      share|improve this question









      New contributor




      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      This is my implementation of a string class similar to std::string and supports the following:




      1. range-for loops

      2. iterators

      3. basic utility functions

      4. exponential capacity increase

      5. operator overloads


      I am looking for specific reviews on implementation of iterators, exponential capacity increase, usage of noexcept and implementations of some functions like resize(), operator+=, operator=, and operator+.



      mystring.h



      #ifndef MY_STRING_H_
      #define MY_STRING_H_

      #include <cstring>
      #include <iostream>
      #include <memory>
      #include <algorithm>
      #include <stdexcept>
      #include <limits>

      namespace kapil {
      class string final {
      private:
      // default capacity of empty string.
      //Minimum default_capacity is 2.
      static constexpr size_t default_capacity_ = 16;
      // current capacity of the string container.
      size_t current_capacity_;
      //size of string.
      size_t sz_;
      // pointer to character
      std::unique_ptr<char> ptr_;
      public:
      string();
      string(const string&);
      string(string&&) noexcept;
      string(const char*);
      explicit string(char);
      ~string() noexcept;

      size_t capacity() const noexcept;
      size_t size() const noexcept;
      size_t length() const noexcept;
      void resize(size_t, char ch = '');
      void clear() noexcept;
      bool empty() const noexcept;
      char& at(size_t);
      const char& at(size_t) const;
      char& back();
      const char& back() const;
      char& front();
      const char& front() const;
      string& append(const string&);
      string& append(const char*);
      string& append(string&&);
      string& append(char);
      void push_back(char);
      string& assign(const string&);
      string& assign(const char*);
      string& assign(string&&);
      void swap(string&);
      const char* c_str() const noexcept;
      const char* data() const noexcept;



      string& operator = (const string&);
      string& operator = (string&&) noexcept;
      string& operator = (const char*);
      string& operator = (char);
      string& operator += (const string&);
      string& operator += (const char*);
      string& operator += (char);
      string& operator += (string&&);
      char& operator (size_t);
      const char& operator (size_t) const;

      friend std::ostream& operator << (std::ostream&, const string&);
      friend string operator + (const string&, const string&);
      friend string operator + (const string&, const char*);
      friend string operator + (const char*, const string&);
      friend string operator + (string&&, string&&);
      friend string operator + (string&&, const char*);
      friend string operator + (const char*, string&&);
      friend string operator + (const string&, string&&);
      friend string operator + (string&&, const string&);
      friend string operator + (const string&, char);
      friend string operator + (char, const string&);
      friend string operator + (string&&, char);
      friend string operator + (char, string&&);
      friend bool operator == (const string&, const string&) noexcept;
      friend bool operator == (const string&, const char*) noexcept;
      friend bool operator == (const char*, const string&) noexcept;
      friend bool operator == (const string&, char) noexcept;
      friend bool operator == (char, const string&) noexcept;
      friend bool operator == (const string&, string&&) noexcept;
      friend bool operator == (string&&, const string&) noexcept;
      friend bool operator == (string&&, string&&) noexcept;
      friend bool operator == (string&&, char) noexcept;
      friend bool operator == (char, string&&) noexcept;
      friend bool operator == (const char*, string&&) noexcept;
      friend bool operator == (string&&, const char*) noexcept;
      friend bool operator != (const string&, const string&) noexcept;
      friend bool operator != (const string&, const char*) noexcept;
      friend bool operator != (const char*, const string&) noexcept;
      friend bool operator != (const string&, char) noexcept;
      friend bool operator != (char, const string&) noexcept;
      friend bool operator != (const string&, string&&) noexcept;
      friend bool operator != (string&&, const string&) noexcept;
      friend bool operator != (string&&, string&&) noexcept;
      friend bool operator != (string&&, char) noexcept;
      friend bool operator != (char, string&&) noexcept;
      friend bool operator != (const char*, string&&) noexcept;
      friend bool operator != (string&&, const char*) noexcept;

      class iterator
      {
      private:
      string* str_;
      size_t index_;
      public:
      iterator(string* = nullptr, size_t = 0) noexcept;
      iterator(const iterator&) noexcept;
      iterator(iterator&&) noexcept;
      ~iterator() noexcept;

      iterator& operator = (const iterator&) noexcept;
      iterator& operator = (iterator&&) noexcept;
      bool operator != (const iterator&) const noexcept;
      bool operator == (const iterator&) const noexcept;
      iterator& operator ++ () noexcept;
      iterator& operator ++ (int) noexcept;
      iterator& operator -- () noexcept;
      iterator& operator -- (int) noexcept;
      char& operator * () const;
      };

      iterator begin();
      iterator end();

      class const_iterator
      {
      private:
      const string* str_;
      size_t index_;
      public:
      const_iterator(const string*, size_t) noexcept;
      const_iterator(const const_iterator&) noexcept;
      const_iterator(const_iterator&&) noexcept;
      ~const_iterator() noexcept;

      const_iterator& operator = (const const_iterator&) noexcept;
      const_iterator& operator = (const_iterator&&) noexcept;
      bool operator != (const const_iterator&) const noexcept;
      bool operator == (const const_iterator&) const noexcept;
      const_iterator& operator ++ () noexcept;
      const_iterator& operator ++ (int) noexcept;
      const_iterator& operator -- () noexcept;
      const_iterator& operator -- (int) noexcept;
      const char& operator * () const;
      };

      const_iterator cbegin();
      const_iterator cend();

      class reverse_iterator
      {
      private:
      string* str_;
      size_t index_;
      public:
      reverse_iterator(string* = nullptr, size_t = 0) noexcept;
      reverse_iterator(const reverse_iterator&) noexcept;
      reverse_iterator(reverse_iterator&&) noexcept;
      ~reverse_iterator() noexcept;

      reverse_iterator& operator = (const reverse_iterator&) noexcept;
      reverse_iterator& operator = (reverse_iterator&&) noexcept;
      bool operator != (const reverse_iterator&) const noexcept;
      bool operator == (const reverse_iterator&) const noexcept;
      reverse_iterator& operator ++ () noexcept;
      reverse_iterator& operator ++ (int) noexcept;
      reverse_iterator& operator -- () noexcept;
      reverse_iterator& operator -- (int) noexcept;
      char& operator * () const;
      };

      reverse_iterator rbegin();
      reverse_iterator rend();

      class reverse_const_iterator
      {
      private:
      const string* str_;
      size_t index_;
      public:
      reverse_const_iterator(const string*, size_t) noexcept;
      reverse_const_iterator(const reverse_const_iterator&) noexcept;
      reverse_const_iterator(reverse_const_iterator&&) noexcept;
      ~reverse_const_iterator() noexcept;

      reverse_const_iterator& operator = (const reverse_const_iterator&) noexcept;
      reverse_const_iterator& operator = (reverse_const_iterator&&) noexcept;
      bool operator != (const reverse_const_iterator&) const noexcept;
      bool operator == (const reverse_const_iterator&) const noexcept;
      reverse_const_iterator& operator ++ () noexcept;
      reverse_const_iterator& operator ++ (int) noexcept;
      reverse_const_iterator& operator -- () noexcept;
      reverse_const_iterator& operator -- (int) noexcept;
      const char& operator * () const;
      };

      reverse_const_iterator crbegin();
      reverse_const_iterator crend();
      };
      } //kapil

      #endif


      my_string.cpp



      #include "my_string.h"

      namespace kapil {
      /*
      For the given new_string_length, the appropriate capacity is the
      next power of 2 that is greater than new_string_length.
      */
      size_t get_appropriate_capacity(size_t new_string_length) {
      size_t appropriate_capacity = 16;
      if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
      appropriate_capacity = new_string_length;
      } else {
      appropriate_capacity = 16;
      if (appropriate_capacity <= new_string_length) {
      if (!(new_string_length & (new_string_length - 1))) {
      appropriate_capacity = new_string_length << 1;
      } else {
      while (appropriate_capacity < new_string_length) {
      appropriate_capacity <<= 1;
      }
      }
      }
      }
      return appropriate_capacity;
      }

      /**************************************** member functions *********************/

      string::string()
      : current_capacity_{ default_capacity_ } {
      sz_ = 0;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = '';
      }

      string::string(const string& other) {
      current_capacity_ = other.current_capacity_;
      sz_ = other.sz_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(ptr_.get(), other.ptr_.get());
      }

      string::string(string&& rval) noexcept
      : current_capacity_{ rval.current_capacity_ },
      sz_{ rval.sz_ },
      ptr_{ std::move(rval.ptr_) } {
      }

      string::string(const char* c_string) {
      sz_ = std::strlen(c_string);
      current_capacity_ = get_appropriate_capacity(sz_);
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(ptr_.get(), c_string);
      }

      string::string(char ch) {
      sz_ = 1;
      current_capacity_ = default_capacity_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = ch;
      ptr_.get()[1] = '';
      }

      string::~string() noexcept {
      current_capacity_ = 0;
      sz_ = 0;
      ptr_.reset(nullptr);
      };


      /**************************************** member functions *********************/

      size_t string::capacity() const noexcept {
      return current_capacity_;
      }

      size_t string::size() const noexcept {
      return sz_;
      }

      size_t string::length() const noexcept {
      return sz_;
      }

      void string::resize(size_t n, char ch) {
      if (n == sz_) {
      return;
      }

      size_t appropriate_capacity = get_appropriate_capacity(n);

      std::unique_ptr<char> temp;
      auto resized = bool{false};

      if (current_capacity_ != appropriate_capacity) {
      resized = true;
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      }

      if (n < sz_) {
      if (resized) {
      std::strncpy(temp.get(), ptr_.get(), n);
      temp.get()[n] = '';
      } else {
      ptr_.get()[n] = '';
      }
      } else if (n > sz_) {
      if (resized) {
      std::strncpy(temp.get(), ptr_.get(), sz_);
      std::fill(temp.get() + sz_, temp.get() + n, ch);
      temp.get()[n] = '';
      } else {
      std::fill(ptr_.get() + sz_, ptr_.get() + n, ch);
      ptr_.get()[n] = '';
      }
      }

      sz_ = n;
      if (resized) {
      ptr_ = std::move(temp);
      }
      }

      void string::clear() noexcept {
      current_capacity_ = default_capacity_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = '';
      sz_ = 0;
      }

      bool string::empty() const noexcept {
      return sz_ == 0;
      }

      char& string::at(size_t idx) {
      if (idx < 0 || idx >= sz_) {
      throw std::out_of_range{"out of range memory access"};
      }
      return (*this)[idx];
      }

      const char& string::at(size_t idx) const {
      if (idx < 0 || idx >= sz_) {
      throw std::out_of_range{"out of range memory access"};
      }
      return (*this)[idx];
      }

      char& string::back() {
      return (*this)[sz_ - 1];
      }

      const char& string::back() const {
      return (*this)[sz_ - 1];
      }

      char& string::front() {
      return (*this)[0];
      }

      const char& string::front() const {
      return (*this)[0];
      }

      string& string::append(const string& rhs) {
      (*this) += rhs;
      return *this;
      }

      string& string::append(const char* rhs) {
      (*this) += rhs;
      return *this;
      }

      string& string::append(string&& rhs) {
      (*this) += rhs;
      return *this;
      }

      string& string::append(char ch) {
      (*this) += ch;
      return *this;
      }

      void string::push_back(char ch) {
      (*this) += ch;
      return;
      }

      string& string::assign(const string& rhs) {
      (*this) = rhs;
      return *this;
      }

      string& string::assign(const char* rhs) {
      (*this) = rhs;
      return *this;
      }

      string& string::assign(string&& rhs) {
      (*this) = rhs;
      return *this;
      }

      void string::swap(string &str) {
      string temp{str};
      str = *this;
      *this = temp;
      }

      const char* string::c_str() const noexcept {
      return ptr_.get();
      }

      const char* string::data() const noexcept {
      return c_str();
      }



      /**************************************** member operator overloads*********************/

      string& string::operator = (const string& rhs) {
      if (this != &rhs) {
      if (current_capacity_ != rhs.current_capacity_) {
      current_capacity_ = rhs.current_capacity_;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      }
      sz_ = rhs.sz_;
      std::strcpy(ptr_.get(), rhs.c_str());
      }
      return *this;
      }

      string& string::operator = (string&& rval) noexcept {
      current_capacity_ = rval.current_capacity_;
      sz_ = rval.sz_;
      ptr_ = std::move(rval.ptr_);
      return *this;
      }

      string& string::operator = (const char* c_string) {
      sz_ = std::strlen(c_string);
      auto appropriate_capacity = get_appropriate_capacity(sz_);
      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      }
      std::strcpy(ptr_.get(), c_string);
      return *this;
      }

      string& string::operator = (char ch) {
      current_capacity_ = default_capacity_;
      sz_ = 1;
      ptr_ = std::make_unique<char>(current_capacity_ + 1);
      ptr_.get()[0] = ch;
      ptr_.get()[1] = '';
      return *this;
      }

      string& string::operator += (const string& rhs) {
      std::unique_ptr<char> temp;
      auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs.sz_);

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }

      std::strcpy(ptr_.get() + sz_, rhs.c_str());
      sz_ += rhs.sz_;

      return *this;
      }

      string& string::operator += (const char* rhs) {
      std::unique_ptr<char> temp;
      auto rhs_sz = std::strlen(rhs);
      auto appropriate_capacity = get_appropriate_capacity(sz_ + rhs_sz);

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }

      std::strcpy(ptr_.get() + sz_, rhs);
      sz_ += rhs_sz;

      return *this;
      }

      string& string::operator += (char ch) {
      auto appropriate_capacity = get_appropriate_capacity(sz_ + 1);
      std::unique_ptr<char> temp;

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }
      ptr_.get()[sz_] = ch;
      ptr_.get()[sz_ + 1] = '';
      sz_ += 1;

      return *this;
      }


      string& string::operator += (string&& rval) {
      std::unique_ptr<char> temp;
      auto appropriate_capacity = get_appropriate_capacity(sz_ + rval.sz_);

      if (current_capacity_ != appropriate_capacity) {
      current_capacity_ = appropriate_capacity;
      temp = std::make_unique<char>(current_capacity_ + 1);
      std::strcpy(temp.get(), ptr_.get());
      ptr_ = std::move(temp);
      }

      std::strcpy(ptr_.get() + sz_, rval.c_str());
      sz_ += rval.sz_;

      return *this;
      }

      char& string::operator (size_t idx) {
      return ptr_.get()[idx];
      }

      const char& string::operator (size_t idx) const {
      return ptr_.get()[idx];
      }



      /**************************************** friend operator overloads *********************/

      std::ostream& operator << (std::ostream& out, const string& str) {
      if (str.size() > 0) {
      out.write(str.c_str(), str.size());
      }
      return out;
      }

      string operator + (const string& lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const string& lhs, const char* rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const char* lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, const char* rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const char* lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const string& lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (const string& lhs, char rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (char lhs, const string& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (string&& lhs, char rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      string operator + (char lhs, string&& rhs) {
      string temp{lhs};
      temp += rhs;
      return temp;
      }

      bool operator == (const string& lhs, const string& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (const string& lhs, const char* rhs) noexcept {
      return (lhs.sz_ == std::strlen(rhs)) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
      }

      bool operator == (const char* lhs, const string& rhs) noexcept {
      return (strlen(lhs) == rhs.sz_) &&
      ((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
      }

      bool operator == (const string& lhs, char rhs) noexcept {
      return (lhs.sz_ == 1) &&
      (lhs.ptr_.get()[0] == rhs);
      }

      bool operator == (char lhs, const string& rhs) noexcept {
      return (rhs.sz_ == 1) &&
      (lhs == rhs.ptr_.get()[0]);
      }

      bool operator == (const string& lhs, string&& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (string&& lhs, const string& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (string&& lhs, string&& rhs) noexcept {
      return (lhs.sz_ == rhs.sz_) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) == 0));
      }

      bool operator == (string&& lhs, char rhs) noexcept {
      return (lhs.sz_ == 1) &&
      (lhs.ptr_.get()[0] == rhs);
      }

      bool operator == (char lhs, string&& rhs) noexcept {
      return (rhs.sz_ == 1) &&
      (rhs.ptr_.get()[0] == lhs);
      }

      bool operator == (string&& lhs, const char* rhs) noexcept {
      return (lhs.sz_ == std::strlen(rhs)) &&
      ((lhs.sz_ == 0) ? true : (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) == 0));
      }

      bool operator == (const char* lhs, string && rhs) noexcept {
      return (std::strlen(lhs) == rhs.sz_) &&
      ((rhs.sz_ == 0) ? true : (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) == 0));
      }

      bool operator != (const string& lhs, const string& rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const string& lhs, const char* rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const char* lhs, const string& rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const string& lhs, char rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (char lhs, const string& rhs) noexcept {
      return !(lhs == rhs);
      }

      bool operator != (const string& lhs, string&& rhs) noexcept {
      return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
      }

      bool operator != (string&& lhs, const string& rhs) noexcept {
      return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
      }

      bool operator != (string&& lhs, string&& rhs) noexcept {
      return (lhs.sz_ != rhs.sz_) || (std::strncmp(lhs.ptr_.get(), rhs.ptr_.get(), lhs.sz_) != 0);
      }

      bool operator != (string&& lhs, char rhs) noexcept {
      return (lhs.sz_ != 1) || (lhs.ptr_.get()[0] != rhs);
      }

      bool operator != (char lhs, string&& rhs) noexcept {
      return (rhs.sz_ != 1) || (rhs.ptr_.get()[0] != lhs);
      }

      bool operator != (string&& lhs, const char* rhs) noexcept {
      return (lhs.sz_ != std::strlen(rhs)) || (std::strncmp(lhs.ptr_.get(), rhs, lhs.sz_) != 0);
      }

      bool operator != (const char* lhs, string && rhs) noexcept {
      return (std::strlen(lhs) != rhs.sz_) || (std::strncmp(lhs, rhs.ptr_.get(), rhs.sz_) != 0);
      }



      /**************************************** iterator related implementations *********************/

      using iterator = string::iterator;

      iterator::iterator(string *str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      iterator::iterator(const iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      iterator::iterator(iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      iterator::~iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      iterator& iterator::operator = (const iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      iterator& iterator::operator = (iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool iterator::operator != (const iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool iterator::operator == (const iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      iterator& iterator::operator ++ () noexcept {
      ++index_;
      return *this;
      }

      iterator& iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      iterator& iterator::operator -- () noexcept {
      --index_;
      return *this;
      }

      iterator& iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      char& iterator::operator * () const {
      return (*str_)[index_];
      }

      iterator string::begin() {
      return iterator(this);
      }

      iterator string::end() {
      return iterator(this, sz_);
      }



      using const_iterator = string::const_iterator;

      const_iterator::const_iterator(const string* str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      const_iterator::const_iterator(const const_iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      const_iterator::const_iterator(const_iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      const_iterator::~const_iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      const_iterator& const_iterator::operator = (const const_iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      const_iterator& const_iterator::operator = (const_iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool const_iterator::operator != (const const_iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool const_iterator::operator == (const const_iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      const_iterator& const_iterator::operator ++ () noexcept {
      ++index_;
      return *this;
      }

      const_iterator& const_iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      const_iterator& const_iterator::operator -- () noexcept {
      --index_;
      return *this;
      }

      const_iterator& const_iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      const char& const_iterator::operator * () const {
      return (*str_)[index_];
      }

      const_iterator string::cbegin() {
      return const_iterator(this, 0);
      }

      const_iterator string::cend() {
      return const_iterator(this, sz_);
      }



      using reverse_iterator = string::reverse_iterator;

      reverse_iterator::reverse_iterator(string *str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      reverse_iterator::reverse_iterator(const reverse_iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      reverse_iterator::reverse_iterator(reverse_iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      reverse_iterator::~reverse_iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      reverse_iterator& reverse_iterator::operator = (const reverse_iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      reverse_iterator& reverse_iterator::operator = (reverse_iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool reverse_iterator::operator != (const reverse_iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool reverse_iterator::operator == (const reverse_iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      reverse_iterator& reverse_iterator::operator ++ () noexcept {
      --index_;
      return *this;
      }

      reverse_iterator& reverse_iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      reverse_iterator& reverse_iterator::operator -- () noexcept {
      ++index_;
      return *this;
      }

      reverse_iterator& reverse_iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      char& reverse_iterator::operator * () const {
      return (*str_)[index_];
      }

      reverse_iterator string::rbegin() {
      return reverse_iterator(this, sz_ - 1);
      }

      reverse_iterator string::rend() {
      return reverse_iterator(this, -1);
      }



      using reverse_const_iterator = string::reverse_const_iterator;

      reverse_const_iterator::reverse_const_iterator(const string* str, size_t index) noexcept
      : str_{str}, index_{index} {
      }

      reverse_const_iterator::reverse_const_iterator(const reverse_const_iterator& itr) noexcept
      : str_{itr.str_}, index_{itr.index_} {
      }

      reverse_const_iterator::reverse_const_iterator(reverse_const_iterator&& rval) noexcept
      : str_{rval.str_}, index_{rval.index_} {
      }

      reverse_const_iterator::~reverse_const_iterator() noexcept {
      str_ = nullptr;
      index_ = 0;
      }



      reverse_const_iterator& reverse_const_iterator::operator = (const reverse_const_iterator& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator = (reverse_const_iterator&& rhs) noexcept {
      str_ = rhs.str_;
      index_ = rhs.index_;
      return *this;
      }

      bool reverse_const_iterator::operator != (const reverse_const_iterator& rhs) const noexcept {
      return (str_ != rhs.str_) || (index_ != rhs.index_);
      }

      bool reverse_const_iterator::operator == (const reverse_const_iterator& rhs) const noexcept {
      return (str_ == rhs.str_) && (index_ == rhs.index_);
      }

      reverse_const_iterator& reverse_const_iterator::operator ++ () noexcept {
      --index_;
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator ++ (int dummy) noexcept {
      ++(*this);
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator -- () noexcept {
      ++index_;
      return *this;
      }

      reverse_const_iterator& reverse_const_iterator::operator -- (int dummy) noexcept {
      --(*this);
      return *this;
      }

      const char& reverse_const_iterator::operator * () const {
      return (*str_)[index_];
      }

      reverse_const_iterator string::crbegin() {
      return reverse_const_iterator(this, sz_ - 1);
      }

      reverse_const_iterator string::crend() {
      return reverse_const_iterator(this, -1);
      }
      } //kapil


      Here is a test file which shows the usage:



      test_string.cpp



      #include "my_string.h"

      using namespace kapil;

      int main() {
      string x{"kapil"};
      std::cout << " size : " << x.size() << " length : " << x.length() << " is empty : " << x.empty() << " at 2 : " << x.at(2) << " back : " <<
      x.back() << " c_str : " << x.c_str() << " data : " << x.data() << std::endl;
      x.clear();
      x = "dev";
      string y{" singh"};
      x.append(y);
      x.append(" is");
      y.assign(" assigned");
      x += " operator +";
      x += y;

      std::cout << " x : " << x << " x cap : " << x.capacity() << "n y : " << y << " y cap : " << y.capacity() << std::endl;
      string added = "i am binary + " + y + string{" ravl add "} + 'x';
      std::cout << " added : " << added << " added cap : " << added.capacity() << std::endl;
      added = "kapil";
      added.resize(10, 'k');
      std::cout << " added resize 10 : " << added << " added cap : " << added.capacity() << std::endl;
      added.resize(78, 'l');
      std::cout << " added resize 78 : " << added << " added cap : " << added.capacity() << std::endl;
      string s1 = "kapil";
      s1.swap(added);
      std::cout << " added : " << added << " s1 : " << s1 << std::endl;

      for (auto it : s1) {
      std::cout << it << " ";
      }

      std::cout << "n";
      return 0;
      }






      c++ strings c++11






      share|improve this question









      New contributor




      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question









      New contributor




      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question








      edited Jan 1 at 21:41









      Jamal

      30.3k11116226




      30.3k11116226






      New contributor




      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked Jan 1 at 19:58









      kapil

      184




      184




      New contributor




      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      kapil is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          1 Answer
          1






          active

          oldest

          votes


















          4














          Errors / bugs / warnings



          So the first thing I did was try to compile your code. Baring in mind my command was:



          g++ -std=c++14 -pedantic -Wall -Wextra -Werror


          And then some more extra warnings. (I assume your program is C++ 14 since std::make_unique was added in C++ 14)



          Here are the list of errors:



          error: no previous declaration for ‘size_t kapil::get_appropriate_capacity(size_t)’


          That function should be in an anonymous namespace or declared static, since it's a local helper function.



          (In "std::size_t get_appropriate_capacity(std::size_t)")
          error: useless cast to type ‘long unsigned int’
          if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
          ^


          I see what your trying to do here, but the way you go about it is not quite right. To check for an unsigned overflow here, the easiest way would be to check if the number is smaller after bitshifting:



          if ((new_string_length << 1) < new_string_length) {


          A more general solution would be:



          if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) {
          // So `new_string_length << a` would have been greater than SIZE_T_MAX




          (After "string::~string() noexcept")
          error: extra ‘;’


          You just have an extra ; that most compilers ignore. Simple fix, remove it.



          error: comparison of unsigned expression < 0 is always false
          if (idx < 0 || idx >= sz_) {
          ~~~~^~~


          As the error says, since idx is a std::size_t (Unsigned), it can never be less than 0. Just remove the check.



          (In "std::ostream& operator <<(std::ostream&, const string&)")
          error: conversion to ‘std::streamsize {aka long int}’ from ‘size_t {aka long unsigned int}’ may change the sign of the result
          out.write(str.c_str(), str.size());
          ~~~~~~~~^~


          For (convoluted) reasons, std::ostream::write takes a signed number for it's count. Just static_cast<std::stream_size>(str.size()).



          (In "string::reverse_iterator string::rend()")
          error: negative integer implicitly converted to unsigned type
          return reverse__iterator(this, -1);
          ~^


          So you obviously want the index before 0 to be the end. This works, but the index before 0 is SIZE_T_MAX. But to show clearly that this is what you wanted, you should have casted to a std::size_t: std::size_t{-1} or static_cast<std::size_t>(-1).



          Another warning I have: Use of ::size_t (size_t in the global namespace). This is not defined in the C++ standard. You should instead use std::size_t to be compatible with all conforming compilers.



          Another bug is in your definition of operator++(int) and operator--(int) for various classes. The "normal" definition is: return a copy of the current value, but increment the value (not the copy).



          So, it is should look something like this:



          T T::operator++(int) noexcept {
          T copy = *this;
          ++*this;
          return copy;
          }


          Your current implementation just means that ++it and it++ do the same thing.



          As for noexcept, the following functions should be noexcept:



          static std::size_t get_appropriate_capacity(std::size_t)
          char& string::back();
          const char& string::back() const;
          char& string::front();
          const char& string::front() const;
          string& string::assign(string&& rhs);
          // Also that one should be:
          string& string::assign(string&& rhs) noexcept { (*this) = std::move(rhs); return *this; }
          // Otherwise it doesn't move (`rhs` is an lvalue)
          char& string::operator(std::size_t); // Though this one's debatable
          const char& string::operator(std::size_t) const; // Same with this one


          Your void string::clear() should not be noexcept. It calls std::make_unique, which could throw. If you do want to make a noexcept version, you could do something like this:



          void string::clear() noexcept {
          try {
          ptr_ = std::make_unique<char>(default_capacity_ + 1);
          current_capacity_ = default_capacity_;
          } catch (const std::bad_alloc&) { /* Reuse old capacity */ }
          ptr_.get()[0] = '';
          sz_ = 0;
          }


          Consider removing string& string::append(string&& rhs); and string& string::operator+=(string&&) (Since you make a copy anyways).



          Also, all your operator== which take string&& are unnecessary. An rvalue can bind to a const string& just fine, and you don't mutate, let alone move, the arguments anyways, so there just isn't any point.



          Consider reimplementing void string::swap(string&). Currently, it makes a temporary copy. You could just swap the data pointers, and swap the members. This would also be noexcecpt.



          void string::swap(string& rhs) noexcept {
          using std::swap;
          swap(current_capacity_, rhs.current_capacity_);
          swap(sz_, rhs.sz_);
          swap(ptr_, rhs.ptr_);
          }


          Also, have a look where you use std::strcpy. This is guaranteed to not do what you expect if a string has a '' in it. E.g.: string s = string{""}+''+''; string copy = s; copy == s // false. Use memcpy with the number of characters you are copying (Usually the fix would be: std::strcpy(a, other.ptr_.get()) -> std::memcpy(a, other.ptr_.get(), other.sz_ + 1); // + 1 for nul terminator)



          These functions should be const:



          const_iterator string::cbegin() const;
          const_iterator string::cend() const;
          const_reverse_iterator string::crbegin() const;
          const_reverse_iterator string::crend() const;


          Design



          You don't overload operator< and friends, so it's hard to compare strings.



          As for how you handle memory allocation, you are a bit too eager. Reallocation is expensive (As it is allocate + copy in this case). You should only use get_appropriate_capacity when the new size would be bigger than the current capacity.



          As for your get_appropriate_capacity function itself, it seems pretty good.



          As a side note, try using std::reverse_iterator instead of implementing a completely seperate class. Also, I don't know if you were reimplementing it on purpose, but string::iterator can just be char*, and string::const_iterator as const char*.



          You might also want to expose current_capacity_ as a member function like: std::size_t string::capacity() const noexcept { return current_capacity_; }



          Also, to actually make your class "Swappable", you need to have a free function swap:



          // .h file
          namespace kapil {
          void swap(string&, string&) noexcept;
          }
          // .cpp file
          namespace kapil {
          void swap(string& rhs, string& lhs) noexcept {
          rhs.swap(lhs);
          }
          }


          As a next goal, try to implement custom CharTs (Have a template class, so you can use wchar instead of char easily), custom "CharTraits" (Another template argument, like std::char_traits<CharT>) and finally custom "Allocators" as the third template argument. These are the 3 template arguments that are used with std::basic_string (std::string is std::basic_string<char, std::char_traits<char>, std::allocator<char>>)






          share|improve this answer























          • Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
            – kapil
            2 days ago










          • @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
            – Artyer
            yesterday











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          kapil is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210707%2fc-string-class-implementation-providing-support-for-various-c11-features%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          4














          Errors / bugs / warnings



          So the first thing I did was try to compile your code. Baring in mind my command was:



          g++ -std=c++14 -pedantic -Wall -Wextra -Werror


          And then some more extra warnings. (I assume your program is C++ 14 since std::make_unique was added in C++ 14)



          Here are the list of errors:



          error: no previous declaration for ‘size_t kapil::get_appropriate_capacity(size_t)’


          That function should be in an anonymous namespace or declared static, since it's a local helper function.



          (In "std::size_t get_appropriate_capacity(std::size_t)")
          error: useless cast to type ‘long unsigned int’
          if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
          ^


          I see what your trying to do here, but the way you go about it is not quite right. To check for an unsigned overflow here, the easiest way would be to check if the number is smaller after bitshifting:



          if ((new_string_length << 1) < new_string_length) {


          A more general solution would be:



          if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) {
          // So `new_string_length << a` would have been greater than SIZE_T_MAX




          (After "string::~string() noexcept")
          error: extra ‘;’


          You just have an extra ; that most compilers ignore. Simple fix, remove it.



          error: comparison of unsigned expression < 0 is always false
          if (idx < 0 || idx >= sz_) {
          ~~~~^~~


          As the error says, since idx is a std::size_t (Unsigned), it can never be less than 0. Just remove the check.



          (In "std::ostream& operator <<(std::ostream&, const string&)")
          error: conversion to ‘std::streamsize {aka long int}’ from ‘size_t {aka long unsigned int}’ may change the sign of the result
          out.write(str.c_str(), str.size());
          ~~~~~~~~^~


          For (convoluted) reasons, std::ostream::write takes a signed number for it's count. Just static_cast<std::stream_size>(str.size()).



          (In "string::reverse_iterator string::rend()")
          error: negative integer implicitly converted to unsigned type
          return reverse__iterator(this, -1);
          ~^


          So you obviously want the index before 0 to be the end. This works, but the index before 0 is SIZE_T_MAX. But to show clearly that this is what you wanted, you should have casted to a std::size_t: std::size_t{-1} or static_cast<std::size_t>(-1).



          Another warning I have: Use of ::size_t (size_t in the global namespace). This is not defined in the C++ standard. You should instead use std::size_t to be compatible with all conforming compilers.



          Another bug is in your definition of operator++(int) and operator--(int) for various classes. The "normal" definition is: return a copy of the current value, but increment the value (not the copy).



          So, it is should look something like this:



          T T::operator++(int) noexcept {
          T copy = *this;
          ++*this;
          return copy;
          }


          Your current implementation just means that ++it and it++ do the same thing.



          As for noexcept, the following functions should be noexcept:



          static std::size_t get_appropriate_capacity(std::size_t)
          char& string::back();
          const char& string::back() const;
          char& string::front();
          const char& string::front() const;
          string& string::assign(string&& rhs);
          // Also that one should be:
          string& string::assign(string&& rhs) noexcept { (*this) = std::move(rhs); return *this; }
          // Otherwise it doesn't move (`rhs` is an lvalue)
          char& string::operator(std::size_t); // Though this one's debatable
          const char& string::operator(std::size_t) const; // Same with this one


          Your void string::clear() should not be noexcept. It calls std::make_unique, which could throw. If you do want to make a noexcept version, you could do something like this:



          void string::clear() noexcept {
          try {
          ptr_ = std::make_unique<char>(default_capacity_ + 1);
          current_capacity_ = default_capacity_;
          } catch (const std::bad_alloc&) { /* Reuse old capacity */ }
          ptr_.get()[0] = '';
          sz_ = 0;
          }


          Consider removing string& string::append(string&& rhs); and string& string::operator+=(string&&) (Since you make a copy anyways).



          Also, all your operator== which take string&& are unnecessary. An rvalue can bind to a const string& just fine, and you don't mutate, let alone move, the arguments anyways, so there just isn't any point.



          Consider reimplementing void string::swap(string&). Currently, it makes a temporary copy. You could just swap the data pointers, and swap the members. This would also be noexcecpt.



          void string::swap(string& rhs) noexcept {
          using std::swap;
          swap(current_capacity_, rhs.current_capacity_);
          swap(sz_, rhs.sz_);
          swap(ptr_, rhs.ptr_);
          }


          Also, have a look where you use std::strcpy. This is guaranteed to not do what you expect if a string has a '' in it. E.g.: string s = string{""}+''+''; string copy = s; copy == s // false. Use memcpy with the number of characters you are copying (Usually the fix would be: std::strcpy(a, other.ptr_.get()) -> std::memcpy(a, other.ptr_.get(), other.sz_ + 1); // + 1 for nul terminator)



          These functions should be const:



          const_iterator string::cbegin() const;
          const_iterator string::cend() const;
          const_reverse_iterator string::crbegin() const;
          const_reverse_iterator string::crend() const;


          Design



          You don't overload operator< and friends, so it's hard to compare strings.



          As for how you handle memory allocation, you are a bit too eager. Reallocation is expensive (As it is allocate + copy in this case). You should only use get_appropriate_capacity when the new size would be bigger than the current capacity.



          As for your get_appropriate_capacity function itself, it seems pretty good.



          As a side note, try using std::reverse_iterator instead of implementing a completely seperate class. Also, I don't know if you were reimplementing it on purpose, but string::iterator can just be char*, and string::const_iterator as const char*.



          You might also want to expose current_capacity_ as a member function like: std::size_t string::capacity() const noexcept { return current_capacity_; }



          Also, to actually make your class "Swappable", you need to have a free function swap:



          // .h file
          namespace kapil {
          void swap(string&, string&) noexcept;
          }
          // .cpp file
          namespace kapil {
          void swap(string& rhs, string& lhs) noexcept {
          rhs.swap(lhs);
          }
          }


          As a next goal, try to implement custom CharTs (Have a template class, so you can use wchar instead of char easily), custom "CharTraits" (Another template argument, like std::char_traits<CharT>) and finally custom "Allocators" as the third template argument. These are the 3 template arguments that are used with std::basic_string (std::string is std::basic_string<char, std::char_traits<char>, std::allocator<char>>)






          share|improve this answer























          • Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
            – kapil
            2 days ago










          • @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
            – Artyer
            yesterday
















          4














          Errors / bugs / warnings



          So the first thing I did was try to compile your code. Baring in mind my command was:



          g++ -std=c++14 -pedantic -Wall -Wextra -Werror


          And then some more extra warnings. (I assume your program is C++ 14 since std::make_unique was added in C++ 14)



          Here are the list of errors:



          error: no previous declaration for ‘size_t kapil::get_appropriate_capacity(size_t)’


          That function should be in an anonymous namespace or declared static, since it's a local helper function.



          (In "std::size_t get_appropriate_capacity(std::size_t)")
          error: useless cast to type ‘long unsigned int’
          if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
          ^


          I see what your trying to do here, but the way you go about it is not quite right. To check for an unsigned overflow here, the easiest way would be to check if the number is smaller after bitshifting:



          if ((new_string_length << 1) < new_string_length) {


          A more general solution would be:



          if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) {
          // So `new_string_length << a` would have been greater than SIZE_T_MAX




          (After "string::~string() noexcept")
          error: extra ‘;’


          You just have an extra ; that most compilers ignore. Simple fix, remove it.



          error: comparison of unsigned expression < 0 is always false
          if (idx < 0 || idx >= sz_) {
          ~~~~^~~


          As the error says, since idx is a std::size_t (Unsigned), it can never be less than 0. Just remove the check.



          (In "std::ostream& operator <<(std::ostream&, const string&)")
          error: conversion to ‘std::streamsize {aka long int}’ from ‘size_t {aka long unsigned int}’ may change the sign of the result
          out.write(str.c_str(), str.size());
          ~~~~~~~~^~


          For (convoluted) reasons, std::ostream::write takes a signed number for it's count. Just static_cast<std::stream_size>(str.size()).



          (In "string::reverse_iterator string::rend()")
          error: negative integer implicitly converted to unsigned type
          return reverse__iterator(this, -1);
          ~^


          So you obviously want the index before 0 to be the end. This works, but the index before 0 is SIZE_T_MAX. But to show clearly that this is what you wanted, you should have casted to a std::size_t: std::size_t{-1} or static_cast<std::size_t>(-1).



          Another warning I have: Use of ::size_t (size_t in the global namespace). This is not defined in the C++ standard. You should instead use std::size_t to be compatible with all conforming compilers.



          Another bug is in your definition of operator++(int) and operator--(int) for various classes. The "normal" definition is: return a copy of the current value, but increment the value (not the copy).



          So, it is should look something like this:



          T T::operator++(int) noexcept {
          T copy = *this;
          ++*this;
          return copy;
          }


          Your current implementation just means that ++it and it++ do the same thing.



          As for noexcept, the following functions should be noexcept:



          static std::size_t get_appropriate_capacity(std::size_t)
          char& string::back();
          const char& string::back() const;
          char& string::front();
          const char& string::front() const;
          string& string::assign(string&& rhs);
          // Also that one should be:
          string& string::assign(string&& rhs) noexcept { (*this) = std::move(rhs); return *this; }
          // Otherwise it doesn't move (`rhs` is an lvalue)
          char& string::operator(std::size_t); // Though this one's debatable
          const char& string::operator(std::size_t) const; // Same with this one


          Your void string::clear() should not be noexcept. It calls std::make_unique, which could throw. If you do want to make a noexcept version, you could do something like this:



          void string::clear() noexcept {
          try {
          ptr_ = std::make_unique<char>(default_capacity_ + 1);
          current_capacity_ = default_capacity_;
          } catch (const std::bad_alloc&) { /* Reuse old capacity */ }
          ptr_.get()[0] = '';
          sz_ = 0;
          }


          Consider removing string& string::append(string&& rhs); and string& string::operator+=(string&&) (Since you make a copy anyways).



          Also, all your operator== which take string&& are unnecessary. An rvalue can bind to a const string& just fine, and you don't mutate, let alone move, the arguments anyways, so there just isn't any point.



          Consider reimplementing void string::swap(string&). Currently, it makes a temporary copy. You could just swap the data pointers, and swap the members. This would also be noexcecpt.



          void string::swap(string& rhs) noexcept {
          using std::swap;
          swap(current_capacity_, rhs.current_capacity_);
          swap(sz_, rhs.sz_);
          swap(ptr_, rhs.ptr_);
          }


          Also, have a look where you use std::strcpy. This is guaranteed to not do what you expect if a string has a '' in it. E.g.: string s = string{""}+''+''; string copy = s; copy == s // false. Use memcpy with the number of characters you are copying (Usually the fix would be: std::strcpy(a, other.ptr_.get()) -> std::memcpy(a, other.ptr_.get(), other.sz_ + 1); // + 1 for nul terminator)



          These functions should be const:



          const_iterator string::cbegin() const;
          const_iterator string::cend() const;
          const_reverse_iterator string::crbegin() const;
          const_reverse_iterator string::crend() const;


          Design



          You don't overload operator< and friends, so it's hard to compare strings.



          As for how you handle memory allocation, you are a bit too eager. Reallocation is expensive (As it is allocate + copy in this case). You should only use get_appropriate_capacity when the new size would be bigger than the current capacity.



          As for your get_appropriate_capacity function itself, it seems pretty good.



          As a side note, try using std::reverse_iterator instead of implementing a completely seperate class. Also, I don't know if you were reimplementing it on purpose, but string::iterator can just be char*, and string::const_iterator as const char*.



          You might also want to expose current_capacity_ as a member function like: std::size_t string::capacity() const noexcept { return current_capacity_; }



          Also, to actually make your class "Swappable", you need to have a free function swap:



          // .h file
          namespace kapil {
          void swap(string&, string&) noexcept;
          }
          // .cpp file
          namespace kapil {
          void swap(string& rhs, string& lhs) noexcept {
          rhs.swap(lhs);
          }
          }


          As a next goal, try to implement custom CharTs (Have a template class, so you can use wchar instead of char easily), custom "CharTraits" (Another template argument, like std::char_traits<CharT>) and finally custom "Allocators" as the third template argument. These are the 3 template arguments that are used with std::basic_string (std::string is std::basic_string<char, std::char_traits<char>, std::allocator<char>>)






          share|improve this answer























          • Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
            – kapil
            2 days ago










          • @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
            – Artyer
            yesterday














          4












          4








          4






          Errors / bugs / warnings



          So the first thing I did was try to compile your code. Baring in mind my command was:



          g++ -std=c++14 -pedantic -Wall -Wextra -Werror


          And then some more extra warnings. (I assume your program is C++ 14 since std::make_unique was added in C++ 14)



          Here are the list of errors:



          error: no previous declaration for ‘size_t kapil::get_appropriate_capacity(size_t)’


          That function should be in an anonymous namespace or declared static, since it's a local helper function.



          (In "std::size_t get_appropriate_capacity(std::size_t)")
          error: useless cast to type ‘long unsigned int’
          if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
          ^


          I see what your trying to do here, but the way you go about it is not quite right. To check for an unsigned overflow here, the easiest way would be to check if the number is smaller after bitshifting:



          if ((new_string_length << 1) < new_string_length) {


          A more general solution would be:



          if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) {
          // So `new_string_length << a` would have been greater than SIZE_T_MAX




          (After "string::~string() noexcept")
          error: extra ‘;’


          You just have an extra ; that most compilers ignore. Simple fix, remove it.



          error: comparison of unsigned expression < 0 is always false
          if (idx < 0 || idx >= sz_) {
          ~~~~^~~


          As the error says, since idx is a std::size_t (Unsigned), it can never be less than 0. Just remove the check.



          (In "std::ostream& operator <<(std::ostream&, const string&)")
          error: conversion to ‘std::streamsize {aka long int}’ from ‘size_t {aka long unsigned int}’ may change the sign of the result
          out.write(str.c_str(), str.size());
          ~~~~~~~~^~


          For (convoluted) reasons, std::ostream::write takes a signed number for it's count. Just static_cast<std::stream_size>(str.size()).



          (In "string::reverse_iterator string::rend()")
          error: negative integer implicitly converted to unsigned type
          return reverse__iterator(this, -1);
          ~^


          So you obviously want the index before 0 to be the end. This works, but the index before 0 is SIZE_T_MAX. But to show clearly that this is what you wanted, you should have casted to a std::size_t: std::size_t{-1} or static_cast<std::size_t>(-1).



          Another warning I have: Use of ::size_t (size_t in the global namespace). This is not defined in the C++ standard. You should instead use std::size_t to be compatible with all conforming compilers.



          Another bug is in your definition of operator++(int) and operator--(int) for various classes. The "normal" definition is: return a copy of the current value, but increment the value (not the copy).



          So, it is should look something like this:



          T T::operator++(int) noexcept {
          T copy = *this;
          ++*this;
          return copy;
          }


          Your current implementation just means that ++it and it++ do the same thing.



          As for noexcept, the following functions should be noexcept:



          static std::size_t get_appropriate_capacity(std::size_t)
          char& string::back();
          const char& string::back() const;
          char& string::front();
          const char& string::front() const;
          string& string::assign(string&& rhs);
          // Also that one should be:
          string& string::assign(string&& rhs) noexcept { (*this) = std::move(rhs); return *this; }
          // Otherwise it doesn't move (`rhs` is an lvalue)
          char& string::operator(std::size_t); // Though this one's debatable
          const char& string::operator(std::size_t) const; // Same with this one


          Your void string::clear() should not be noexcept. It calls std::make_unique, which could throw. If you do want to make a noexcept version, you could do something like this:



          void string::clear() noexcept {
          try {
          ptr_ = std::make_unique<char>(default_capacity_ + 1);
          current_capacity_ = default_capacity_;
          } catch (const std::bad_alloc&) { /* Reuse old capacity */ }
          ptr_.get()[0] = '';
          sz_ = 0;
          }


          Consider removing string& string::append(string&& rhs); and string& string::operator+=(string&&) (Since you make a copy anyways).



          Also, all your operator== which take string&& are unnecessary. An rvalue can bind to a const string& just fine, and you don't mutate, let alone move, the arguments anyways, so there just isn't any point.



          Consider reimplementing void string::swap(string&). Currently, it makes a temporary copy. You could just swap the data pointers, and swap the members. This would also be noexcecpt.



          void string::swap(string& rhs) noexcept {
          using std::swap;
          swap(current_capacity_, rhs.current_capacity_);
          swap(sz_, rhs.sz_);
          swap(ptr_, rhs.ptr_);
          }


          Also, have a look where you use std::strcpy. This is guaranteed to not do what you expect if a string has a '' in it. E.g.: string s = string{""}+''+''; string copy = s; copy == s // false. Use memcpy with the number of characters you are copying (Usually the fix would be: std::strcpy(a, other.ptr_.get()) -> std::memcpy(a, other.ptr_.get(), other.sz_ + 1); // + 1 for nul terminator)



          These functions should be const:



          const_iterator string::cbegin() const;
          const_iterator string::cend() const;
          const_reverse_iterator string::crbegin() const;
          const_reverse_iterator string::crend() const;


          Design



          You don't overload operator< and friends, so it's hard to compare strings.



          As for how you handle memory allocation, you are a bit too eager. Reallocation is expensive (As it is allocate + copy in this case). You should only use get_appropriate_capacity when the new size would be bigger than the current capacity.



          As for your get_appropriate_capacity function itself, it seems pretty good.



          As a side note, try using std::reverse_iterator instead of implementing a completely seperate class. Also, I don't know if you were reimplementing it on purpose, but string::iterator can just be char*, and string::const_iterator as const char*.



          You might also want to expose current_capacity_ as a member function like: std::size_t string::capacity() const noexcept { return current_capacity_; }



          Also, to actually make your class "Swappable", you need to have a free function swap:



          // .h file
          namespace kapil {
          void swap(string&, string&) noexcept;
          }
          // .cpp file
          namespace kapil {
          void swap(string& rhs, string& lhs) noexcept {
          rhs.swap(lhs);
          }
          }


          As a next goal, try to implement custom CharTs (Have a template class, so you can use wchar instead of char easily), custom "CharTraits" (Another template argument, like std::char_traits<CharT>) and finally custom "Allocators" as the third template argument. These are the 3 template arguments that are used with std::basic_string (std::string is std::basic_string<char, std::char_traits<char>, std::allocator<char>>)






          share|improve this answer














          Errors / bugs / warnings



          So the first thing I did was try to compile your code. Baring in mind my command was:



          g++ -std=c++14 -pedantic -Wall -Wextra -Werror


          And then some more extra warnings. (I assume your program is C++ 14 since std::make_unique was added in C++ 14)



          Here are the list of errors:



          error: no previous declaration for ‘size_t kapil::get_appropriate_capacity(size_t)’


          That function should be in an anonymous namespace or declared static, since it's a local helper function.



          (In "std::size_t get_appropriate_capacity(std::size_t)")
          error: useless cast to type ‘long unsigned int’
          if ((static_cast<unsigned long>(new_string_length) << 1) > std::numeric_limits<size_t>::max()) {
          ^


          I see what your trying to do here, but the way you go about it is not quite right. To check for an unsigned overflow here, the easiest way would be to check if the number is smaller after bitshifting:



          if ((new_string_length << 1) < new_string_length) {


          A more general solution would be:



          if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) {
          // So `new_string_length << a` would have been greater than SIZE_T_MAX




          (After "string::~string() noexcept")
          error: extra ‘;’


          You just have an extra ; that most compilers ignore. Simple fix, remove it.



          error: comparison of unsigned expression < 0 is always false
          if (idx < 0 || idx >= sz_) {
          ~~~~^~~


          As the error says, since idx is a std::size_t (Unsigned), it can never be less than 0. Just remove the check.



          (In "std::ostream& operator <<(std::ostream&, const string&)")
          error: conversion to ‘std::streamsize {aka long int}’ from ‘size_t {aka long unsigned int}’ may change the sign of the result
          out.write(str.c_str(), str.size());
          ~~~~~~~~^~


          For (convoluted) reasons, std::ostream::write takes a signed number for it's count. Just static_cast<std::stream_size>(str.size()).



          (In "string::reverse_iterator string::rend()")
          error: negative integer implicitly converted to unsigned type
          return reverse__iterator(this, -1);
          ~^


          So you obviously want the index before 0 to be the end. This works, but the index before 0 is SIZE_T_MAX. But to show clearly that this is what you wanted, you should have casted to a std::size_t: std::size_t{-1} or static_cast<std::size_t>(-1).



          Another warning I have: Use of ::size_t (size_t in the global namespace). This is not defined in the C++ standard. You should instead use std::size_t to be compatible with all conforming compilers.



          Another bug is in your definition of operator++(int) and operator--(int) for various classes. The "normal" definition is: return a copy of the current value, but increment the value (not the copy).



          So, it is should look something like this:



          T T::operator++(int) noexcept {
          T copy = *this;
          ++*this;
          return copy;
          }


          Your current implementation just means that ++it and it++ do the same thing.



          As for noexcept, the following functions should be noexcept:



          static std::size_t get_appropriate_capacity(std::size_t)
          char& string::back();
          const char& string::back() const;
          char& string::front();
          const char& string::front() const;
          string& string::assign(string&& rhs);
          // Also that one should be:
          string& string::assign(string&& rhs) noexcept { (*this) = std::move(rhs); return *this; }
          // Otherwise it doesn't move (`rhs` is an lvalue)
          char& string::operator(std::size_t); // Though this one's debatable
          const char& string::operator(std::size_t) const; // Same with this one


          Your void string::clear() should not be noexcept. It calls std::make_unique, which could throw. If you do want to make a noexcept version, you could do something like this:



          void string::clear() noexcept {
          try {
          ptr_ = std::make_unique<char>(default_capacity_ + 1);
          current_capacity_ = default_capacity_;
          } catch (const std::bad_alloc&) { /* Reuse old capacity */ }
          ptr_.get()[0] = '';
          sz_ = 0;
          }


          Consider removing string& string::append(string&& rhs); and string& string::operator+=(string&&) (Since you make a copy anyways).



          Also, all your operator== which take string&& are unnecessary. An rvalue can bind to a const string& just fine, and you don't mutate, let alone move, the arguments anyways, so there just isn't any point.



          Consider reimplementing void string::swap(string&). Currently, it makes a temporary copy. You could just swap the data pointers, and swap the members. This would also be noexcecpt.



          void string::swap(string& rhs) noexcept {
          using std::swap;
          swap(current_capacity_, rhs.current_capacity_);
          swap(sz_, rhs.sz_);
          swap(ptr_, rhs.ptr_);
          }


          Also, have a look where you use std::strcpy. This is guaranteed to not do what you expect if a string has a '' in it. E.g.: string s = string{""}+''+''; string copy = s; copy == s // false. Use memcpy with the number of characters you are copying (Usually the fix would be: std::strcpy(a, other.ptr_.get()) -> std::memcpy(a, other.ptr_.get(), other.sz_ + 1); // + 1 for nul terminator)



          These functions should be const:



          const_iterator string::cbegin() const;
          const_iterator string::cend() const;
          const_reverse_iterator string::crbegin() const;
          const_reverse_iterator string::crend() const;


          Design



          You don't overload operator< and friends, so it's hard to compare strings.



          As for how you handle memory allocation, you are a bit too eager. Reallocation is expensive (As it is allocate + copy in this case). You should only use get_appropriate_capacity when the new size would be bigger than the current capacity.



          As for your get_appropriate_capacity function itself, it seems pretty good.



          As a side note, try using std::reverse_iterator instead of implementing a completely seperate class. Also, I don't know if you were reimplementing it on purpose, but string::iterator can just be char*, and string::const_iterator as const char*.



          You might also want to expose current_capacity_ as a member function like: std::size_t string::capacity() const noexcept { return current_capacity_; }



          Also, to actually make your class "Swappable", you need to have a free function swap:



          // .h file
          namespace kapil {
          void swap(string&, string&) noexcept;
          }
          // .cpp file
          namespace kapil {
          void swap(string& rhs, string& lhs) noexcept {
          rhs.swap(lhs);
          }
          }


          As a next goal, try to implement custom CharTs (Have a template class, so you can use wchar instead of char easily), custom "CharTraits" (Another template argument, like std::char_traits<CharT>) and finally custom "Allocators" as the third template argument. These are the 3 template arguments that are used with std::basic_string (std::string is std::basic_string<char, std::char_traits<char>, std::allocator<char>>)







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 2 days ago

























          answered Jan 2 at 1:36









          Artyer

          20618




          20618












          • Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
            – kapil
            2 days ago










          • @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
            – Artyer
            yesterday


















          • Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
            – kapil
            2 days ago










          • @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
            – Artyer
            yesterday
















          Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
          – kapil
          2 days ago




          Dear @Artyer I have some further clarifications : [1] I have not made the following functions as 'noexcept' since these may have undefined behavior for empty strings. char& string::back(); const char& string::back() const; char& string::front(); const char& string::front() const; [2] In your comment if (std::numeric_limits<std::size_t>::max() >> a < new_string_length) { // So new_string_length << a` would have been greater than SIZE_T_MAX` what is 'a' ? I am extremely glad that you took out time to review my code :)
          – kapil
          2 days ago












          @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
          – Artyer
          yesterday




          @kapil a is anything you want. In your case, a would have been 1. That is just a general check for if x << a overflows. Undefined behaviour is still noexcept, as in if the preconditions are met (string is not empty) it will never thow. I personally would make it noexcept, but many people wouldn't. Same reasoning for operator.
          – Artyer
          yesterday










          kapil is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          kapil is a new contributor. Be nice, and check out our Code of Conduct.













          kapil is a new contributor. Be nice, and check out our Code of Conduct.












          kapil is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210707%2fc-string-class-implementation-providing-support-for-various-c11-features%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Список кардиналов, возведённых папой римским Каликстом III

          Deduzione

          Mysql.sock missing - “Can't connect to local MySQL server through socket”