C++ Custom String Class


It is quite complex to implement advanced String classes. Unlike C++ and C#, many modules need to solve their own memory problems, and how to improve the efficiency of memory space, whether calling a function triggers a copy constructor, and how to avoid duplicate memory release when triggering a copy constructor.

There are a few overloads left that have not been implemented, which will be written tomorrow, and the tests and instructions for this type need to be slowly added.

Declaration of class

Use char to implement a string class.
First, define a String class, then define internal member variables and member functions.
Content:
1.

Private MembersExplain
_strCharacter string
_sizeString Length
bool _SetStringEncapsulation Settings_ str and _ size
_logTemporary output information for testing, temporarily using string storage to save time, deleting all log-related code after class implementation
public membersExplain
StringConstructors include default constructors, overloaded character arrays, overloaded copy constructors, and implementation of the move function
~StringDestructor that frees memory when an object is destroyed
String& operator =Overload = operator and implementation of the move function, the difficulty is that it is written as its own recursion
String operator + (not yet implemented)Overload + operator, since the return is a temporary variable, const should be preceded and the function header does not require a reference
String operator +=Overloaded + = operator, should be similar to overloaded = operator
char& operator[]Overload brackets to achieve c=s[0] and s[0]=c
(not yet implemented) istream & operator >>Overloaded input streams need to be studied for efficient writing and \r\n issues
(Not yet implemented) ostream & operator < <Overloaded output stream
String* GetAddressReturn to your address
String GetCopy()Back to Clone
String& GetOriginal()Return to itself
char* c_str()Returns the address of the character array of the string itself, with const added to prevent memory from being modified
int length()Return string length
class String {
private:
	char* _str = NULL;
	size_t _size = 0;
	string _log;
	bool _SetString(const char* str);
public:
	String();
	String(const char *str);
	String(const String& other);
	String(String&& other) noexcept; // TODO: noexcept
	~String();

	// Overload = Operator
	String& operator =(const char* str);
	String& operator =(const String& other);
	String& operator =(String&& other) noexcept; // TODO: noexcept

	// Overload + operator, returns a temporary object, does not require a reference
	const String operator +(const char* str);
	const String operator +(const char ch);
	const String operator +(const String& other);

	// Overload+=Operator
	String operator +=(const char* str);
	String operator +=(const char ch);
	String operator +=(const String& other);

	// Overloaded brackets
	//char operator[](size_t index);
	char& operator[](size_t index); // Can implement str[0]='1'

	// Overload==Operator
	bool operator ==(const char* str);
	bool operator ==(const String& other);

	// Overloaded input-output stream
	friend istream& operator >> (istream& cin, String& str);
	friend ostream& operator << (ostream& cin, String& str);

	//Return to your address
	String* GetAddress() { return this; }
	// Back to Clone
	String GetCopy() { return *this; }
	// Return to itself
	String& GetOriginal() { return *this; }

	// Returns the address of the character array of the string itself, plus const to prevent modification
	const char* c_str() { return _str; }

	// Return string length
	int length() { return _size; }
};

_SetString

bool String::_SetString(const char* str)
{
	try {
		if (str == NULL) {
			_size = 0;
			_str = NULL;
		}
		else {
			if (_str != NULL) {
				delete[] _str;
				_str = NULL;
			}
			_size = strlen(str);
			_str = new char[_size + 1];
			strcpy(_str, str);
			_str[_size] = '\0';
		}
	}
	catch(...){
		return false;
	}
	return true;
}

Constructors and Destructors

String::String() {
	// Variables have been initialized at the declaration, and the destructor is only for output information
	_log = "String()";
	cout << "String()" << endl;
}


String::String(const char* str)
{
	_SetString(str);
	_log = "String(const char* str)";
	cout << "String(const char* str)" << endl;
}

String::String(const String& other)
{
	_SetString(other._str);
	_log = "String(const String& s)";
	cout << "String(const String& s)" << endl;
}

// String's move implementation
String::String(String&& other) noexcept
{
	_size = other._size;
	_str = other._str;
	other._size = 0;
	// This way, other. _will not be recycled while destructing the other. Memory space pointed to by STR
	other._str = NULL;
}

String::~String()
{
	cout << "~String" << _log << endl;
	if (_str != NULL) {
		delete _str;
		_str = NULL;
	}
}

Items to be tested

String s1();
String s2("123");
String s3(s2);
String s4 = s2; // s4 is not instantiated, here equivalent to String s4(s2)
String s5;
s5 = s2; // Trigger = Overload

If no overload=, an error will be reported "trying to reference a deleted function", so it must be overloaded
The conditions that trigger an attempt to reference a deleted function:

  • Contains a non-static const member variable
  • reference member variable with non-static state
  • Contains member variables that cannot be copied
  • Contains a base class that cannot be copied
  • Contains a user-defined move constructor or a move assignment function

See other blogs explaining move (not understood yet):
std::move can force a left-value reference to a right-value reference, except that the transition state does not transfer memory, which is equivalent to static_ Cast <T &&>
The function parameter T & & is a right-value reference to a template type parameter that can be matched to any type of argument by reference collapse

Overload = Operator

String& String::operator=(const char* str)
{
	cout << "operator=(const char* str)" << endl;
	_SetString(str);
	return *this;
}

String& String::operator=(const String& other)
{
	cout << "operator=(const String& other) self" << endl;
	// Check Self-Assignment Points
	if (this != &other) {
		// Release memory, allocate new space

		_SetString(other._str);
	}
	return *this;
}

String& String::operator=(String&& other) noexcept
{
	cout << "operator=(String&& other)" << endl;
	// Check Self-Assignment Points
	if (this != &other) {
		_size = other._size;
		_str = other._str;
		other._size = 0;
		other._str = NULL;
	}
	return *this;
}

Here are the wrong writings, which go into an infinite recursive loop. I naturally assume that *this=String(...) will make the copy I want, but it will trigger the third copy and go into an infinite loop

String& String::operator=(const char* str)
{
	cout << "operator=(const char* str)" << endl;
	*this = String(str);
	return *this;
}

String& String::operator=(const String& other)
{
	cout << "operator=(const String& other)" << endl;
	*this = String(other);
	return *this;
}

String& String::operator=(String&& other) noexcept
{
	cout << "operator=(String&& other)" << endl;
	*this = String(other);
	return *this;
}

To be tested

String s("123");
String s2("456");
String *s3;
s3 = &s2; // Pointer reference
String s4;
s4 = CreateString("111"); // Trigger the third = overload
cout << endl<< s[0] <<endl;
cout << endl<< s2[0] <<endl;
s[0]='9';
cout << endl << s[0] << endl;
cout << endl << s2[0] << endl;
cout << endl << (*s3)[0] << endl;

Overload+Operator

Overload+=Operator

Overloaded brackets

char& String::operator[](size_t index)
{
	return _str[index];
}

Overload==Operator

Overloaded input-output stream

Return Address/Clone/Self

Keywords: C++

Added by Gladiator2043 on Mon, 21 Feb 2022 19:16:22 +0200