4
\$\begingroup\$

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 = '\0');
      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] = '\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] = '\0';
  }

  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] = '\0';
      } else {
        ptr_.get()[n] = '\0';
      }
    } 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] = '\0';
      } else {
        std::fill(ptr_.get() + sz_, ptr_.get() + n, ch);
        ptr_.get()[n] = '\0';
      }
    }

    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] = '\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] = '\0';
    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] = '\0';
    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;
}
\$\endgroup\$
5
\$\begingroup\$

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] = '\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 '\0' in it. E.g.: string s = string{""}+'\0'+'\0'; 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>>)

\$\endgroup\$
  • \$\begingroup\$ 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 :) \$\endgroup\$ – kapil Jan 2 at 18:06
  • \$\begingroup\$ @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[]. \$\endgroup\$ – Artyer Jan 3 at 18:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.