My teacher asked me to come up with a quick version of the C++ string
class. This is what I have written. It works fine and I have tested few cases as detailed in main()
. Now I am curious how good this program is actually in terms of modern C++ fundamentals.
#include <iostream>
#include <exception>
class STR
{
public:
STR(const char* ptr): sz(strlen(ptr)+1),m_data(new (std::nothrow) char[sz])
{
if (m_data)
{
memcpy(m_data, ptr, sz);
std::cout << "STR() - "<< getData() << ":" << getSize() << " chars" << '\n';
}
else
{
std::cerr << "Fatal Error - Cannot construct STR()";
}
}
STR() : m_data(nullptr),sz(0) {}
STR(const STR& obj)
{
sz = strlen(obj) + 1;
m_data = new (std::nothrow) char[sz];
memcpy(m_data, obj, sz);
std::cout << "STR(const STR&) - " << getData() << ":" << getSize() << " chars" << '\n';
}
size_t getSize() const noexcept
{
return sz;
}
char* getData() const
{
return this->m_data;
}
operator const char*() const {
return m_data;
}
STR& operator=(const char* other)
{
if (this->sz != 0 && this->m_data)
{
std::cout << "Destructing..." << getData() << '\n';
this->sz = 0;
delete[] this->m_data;
}
sz = strlen(other) + 1;
m_data = new (std::nothrow) char[sz];
memcpy(m_data, other, sz);
std::cout << "STR(const STR&) - " << getData() << ":" << getSize() << " chars" << '\n';
return *this;
}
STR& operator=(const STR& other)
{
if (this != &other)
{
//Release *this memory
if (this->m_data)
{
std::cout << "Destructing..." << getData() << '\n';
delete[] m_data;
}
sz = other.getSize();
//Allocate *this memory
this->m_data = new (std::nothrow) char[sz];
memcpy(m_data, other.getData(), sz);
std::cout << "operator=(const STR&) - " << getData() << ":" << getSize() << " chars" << '\n';
}
return *this;
}
//Move assignment
STR& operator=(STR&& other)
{
if (this != &other)
{
delete this->m_data;
//char* p = other.getData();
this->m_data = std::exchange(other.m_data, nullptr);
//size_t sz = other.getSize();
this->sz = std::exchange(other.sz, 0);
}
return *this;
}
Particularly here as I had to comment the private
members and make them public as there was no easy way to implement move assignment for STR
.
//private:
size_t sz;
char* m_data;
};
std::ostream& operator<<(std::ostream& os, const STR& str)
{
if (!str.getData())
return (os << "");
os << str.getData();
return os;
}
auto main()->int
{
//Object direct initialization
STR a{"Simple text"};
//Object direct initialization
STR b = "Another text";
STR c = a;
//Empty obj creation
STR d;
std::cout << "d:" << d << '\n';
//Empty object assignment
d = std::move(c);
std::cout << "d:" << d << '\n';
//Existing object assignment
b = d;
std::cout << "b:" << b << '\n';
//Object assigment using const char*
a = "What next";
std::cout << "a:" << a <<'\n';
}
Version 2.0 - Based on all the comments
#include <iostream>
#include <exception>
#include <memory>
class MyString
{
public:
MyString(const char* ptr): sz(strlen(ptr)+1), m_data( sz ? new char[sz]:nullptr)
{
std::copy(ptr, ptr + sz, m_data);
}
MyString() : m_data(nullptr),sz(0) {}
~MyString()
{
delete[] m_data;
}
MyString(const MyString& obj):sz(strlen(obj) + 1), m_data(new char[sz])
{
std::copy(obj.m_data, obj.m_data + sz, m_data);
}
MyString(MyString&& obj) noexcept :sz(obj.sz), m_data(obj.m_data)
{
obj.sz = 0;
obj.m_data = 0;
}
const size_t getSize() const noexcept
{
return sz;
}
const char* getData() const
{
return m_data;
}
operator const char*() const {
return m_data;
}
MyString& operator=(const char* other)
{
MyString tmp(other);
swap(tmp, *this);
return *this;
}
friend void swap(MyString& first, MyString& second)
{
using std::swap;
std::swap(first.m_data, second.m_data);
std::swap(first.sz, second.sz);
}
MyString& operator=(MyString other)
{
MyString tmp(other);
swap(other, *this);
return *this;
}
private:
size_t sz;
char* m_data;
};
std::ostream& operator<<(std::ostream& os, const MyString& str)
{
if (!str.getData())
return (os << "");
os << str.getData();
return os;
}
int main()
{
//Object direct initialization
MyString a{"Simple text"};
//Object direct initialization
MyString b = "Another text";
MyString c = a;
//Empty obj creation
MyString d;
std::cout << "d:" << d << '\n';
//Empty object assignment
d = std::move(c);
std::cout << "d:" << d << '\n';
//Existing object assignment
b = d; ---(1)
std::cout << "b:" << b << '\n';
//Object assigment using const char*
a = "What next";
std::cout << "a:" << a <<'\n';
}
The issue now is that for code line -- (1) , first the copy constructor is called and then assignment operator, I think that copy elision works with rvalue parameters and not with lvalue parameters for unifying assignments of type defined here.
And another todo: is using automated test cases and I am still trying to find out if C++11/14/17 have any test case frameworks or I have to use something external like gtest, cpptest, etc.
char* m_data
andm_data(new (std::nothrow) char[sz])
is not modern c++. If you use a container to keep the char array you wouldn't need to implement any of the move or copy constructors/assignment op. yourself, see this example: ideone.com/3D06K2 \$\endgroup\$