I have been learning about the rule of five and I tried to implement an example myself. It all works exactly as I'd expect, but I was wondering if I've missed any obvious issues or easier/faster ways of implementing the constructors and operators.class string {public:    string() : data(nullptr) {        std::cout << Default ctor << std::endl;    }    string(const char * p, size_t size) : m_size(size) { //default constructor        std::cout << Special ctor << std::endl;        data = new char[m_size];        memcpy(data, p, m_size);    }    ~string() noexcept { //default destructor        std::cout << Default dtor\r\n;        delete[] data;    }    string(const string & str) { //copy constructor        std::cout << Copy constructor << std::endl;        m_size = str.m_size;        data = new char[m_size];        memcpy(data,str.data,m_size);     }    string(string && str)  { //move constructor        std::cout << Move constructor << std::endl;        m_size = str.m_size;        data = str.data; //swap the values        str.data = nullptr;    }    string& operator=(const string & str)  { //copy operator        std::cout << Copy assignment << std::endl;        if (this == &str) return * this;        delete[] data; //can safely delete a nullptr        m_size = str.m_size;        char * new_data = new char[m_size]; //we have to make a copy as this is not the move operator        memcpy(new_data,str.data,m_size);         data = new_data;        return * this;    }    string& operator=(string && str) { //move assignment operator        std::cout << Move assignment << std::endl;        if (this == &str) return * this;        delete[] data; //delete the old data, will be ok because data is always initialised to nullptr        m_size = str.m_size;         data = str.data; //swap over the pointers        str.data = nullptr;         return * this;    }    string& operator+=(const string & str) {        size_t new_size = m_size + str.m_size; //we need to create a new block of memory with the new appropriate size        char * new_data = new char[new_size];        memmove(new_data,data,m_size); //move the old memory        strcat(new_data,str.data); //concat the new memory        delete[] data; //delete the old data        m_size = new_size;        data = new_data;        return *this;    }     string operator+(const string & str) {        return static_cast<string&&>(string(*this) += str);   //could be replaced with std::move() but I use static cast for education purposes    }private:    char * data;    size_t m_size;    friend std::ostream& operator<<(std::ostream& os, const string& s) {        os << s.data << \r\n;        return os;    }};    int main() {        const char t1[] = text;        const char t2[] = more text;        const char * p1 = t1;        const char * p2 = t2;        string a(p1,sizeof(t1));        string b(p2,sizeof(t2));        string c(a);        string d(a + b);        string e;        a += b;        e = d;        c = b + a;        std::cout << a:\t << a;        std::cout << b:\t << b;        std::cout << c:\t << c;        std::cout << d:\t << d;        std::cout << e:\t << e;        return 0;    } I ran it with valgrind and recieved the following output: I am concerned that there are 11 uses of alloc and only 10 frees? I am vaguely familiar with the difference but not confident enough to tell if it's a mistake I've made or expected output?==24703== HEAP SUMMARY:==24703==     in use at exit: 72,704 bytes in 1 blocks==24703==   total heap usage: 11 allocs, 10 frees, 73,833 bytes allocated==24703== ==24703== LEAK SUMMARY:==24703==    definitely lost: 0 bytes in 0 blocks==24703==    indirectly lost: 0 bytes in 0 blocks==24703==      possibly lost: 0 bytes in 0 blocks==24703==    still reachable: 72,704 bytes in 1 blocks==24703==         suppressed: 0 bytes in 0 blocks==24703== Rerun with --leak-check=full to see details of leaked memory==24703== ==24703== For counts of detected and suppressed errors, rerun with: -v==24703== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Rule of five and move semantics for a string class
c++;strings;reinventing the wheel;memory management