3 MyString Class
Published:
Header function: class definition with all member functions declared
- Except for short functions (let compiler
inlineit) - Define them in the
cppfile, prepended withMyString::
mystring.h
class MyString {
public:
MyString();
MyString(const char* p);
~MyString();
MyString(const MyString& s); // copy constructor
MyString& operator=(const MyString& s); // copy assignment
int length() const {return len;}
friend MyString operator+(const MyString& s1, const MyString& s2);
friend std::ostream& operator<<(std::ostream& os, const MyString& s);
friend std::istream& operator>>(std::istream& is, MyString& s);
char& operator[](int i);
const char& operator[](int i) const;
private:
char* data;
int len;
};
Makefile
executables = test1 test2 test3
objects = mystring.o test1.o test2.o test3.o
.PHONY: default
default: $(executables)
$(executables): mystring.o
$(objects): mystring.h
defaulthas all executables we want to build. Will get expanded totest1 test2 test3- For each
$(executables)and$(objects)gets expanded to
test1: test1.o mystring.o
g++ test1.o mystring.o -o test1
test1.o: test1.cpp mystring.h
g++ -g -Wall -std=c++14 -c test1.cpp
The variables will be expanded. *.o and *.cpp ingredients are implied
Basic 4
Don’t forget the
nullptrcase, as well as allocatinglen + 1
#include "mystring.h"
int main() {
using namespace std;
MyString s1;
MyString s2("hello");
MyString s3(s2); // copy construct
s1 = s2; // copy assignment
cout << s1 << "," << s2 << "," << s3 << endl;
}
Constructor
When you initialize "hello":
- Type:
char [6] - Value: pointer at
.rodata, betweencodeanddata- However, string there will be immutable.
- Need to allocate a copy
MyString::MyString(const char* p) {
if (p) {
len = strlen(p);
data = new char[len + 1];
strcpy(data, p);
} else {/* allocate a null string */}
}
new char[6]: allocate 6 elements ofcharon heap- If
newfails to allocate, throws exception
- If
- Can’t allocate on stack due to scope
If string is empty, allocating 1 byte is unfortunate. Why not make it
NULL?
- Creates an invariant property, better testing integrity
- real
std::stringhas a short (E. 32-byte) buffer to avoid heap allocation
Destructor
MyString::~MyString() {
delete[] data;
}
Copy Constructor
Consider this code
void f(MyString s2)
cout << s2;
int main() {
MyString s1("hello");
f(s1);
cout << s1;
}
If copy constructor not defined, s2 will be copied on the stack, member-wise
- When
freturns,s2goes away, and its destructor gets called!!! s1.datadeleted!!!- Shallow copy!
Need make every copy carry its own heap allocated string
MyString::MyString(const MyString& s) {
len = s.len;
data = new char[len+1];
strcpy(data, s.data);
}
Copy Assignment
Need do a little more,
MyString& MyString::operator=(const MyString& rhs) {
// if s1 = s1, DON'T change anything
if (this == &rhs)
return *this;
// first, deallocate memory that 'this' used to hold
delete[] data;
// same as copy constructor
len = rhs.len;
data = new char[len+1];
strcpy(data, rhs.data);
return *this;
}
What if I use
*this == rhs?
- Same result, but not efficient to compare all
structcontents- Depends on
operator==()definition
Rule of 3
- If you implement any of destructor, copy constructor, and copy assignment, you probably need to do all 3 of them
Inline Functions
Entire body defined in mystring.h
- Tell compiler to
inlineit - Compiler may refuse (E. long/recursive/virtual functions)
Operators
operator+()
operator+()is not a member function (nothispointer, noMyString::). It’s global
MyString operator+(const MyString& s1, const MyString& s2) {
MyString temp;
delete[] temp.data;
temp.len = s1.len + s2.len;
temp.data = new char[temp.len+1];
strcpy(temp.data, s1.data);
strcat(temp.data, s2.data);
return temp;
}
How does it access
privatedata?
- Declared as
friendin prototype
friend MyString operator+(const MyString& s1, const MyString& s2);
After return temp, a copy of temp is created, which goes into rhs for operator=()
Why is
operator=()member, butoperator+()global? Either works syntax-wise
operator=()modifies LFS, so make it member- lhs and rhs of
operator+()are symmetrical, so make it global
(s1 + "world") also works
- Compiler first looks for overload of
operator+(MyString, char*) - Next, sees if can promote one of the mismatched argument into
MyString- By invoking constructor
MyString(char*)- Finds a constructor of
MyStringthat takeschar* - Constructs a temporary object from
char*
- Only works if
operator+()is a global function"hello" + "world"doesn’t work
- Finds a constructor of
- By invoking constructor
- Preserves C behavior
operator<<
coutisstd::ostreamthat representsstdout- Return it by reference
- So associativity not violated
friend std::ostream& operator<<(std::ostream& os, const MyString& s);
Why not make it a member function of
std::ostream? Why make it global?
- Don’t want to redefine the code in
std
std::ostream& operator<<(std::ostream& os, const MyString& s) {
os << s.data; // give os the char*
return os; // return BY REFERENCE
}
(cout << s1) << "something else"; // LHS is still cout
operator>>()
friend std::istream& operator>>(std::istream& is, MyString& s);
std::istream& operator>>(std::istream& is, MyString& s) {
std::string temp;
is >> temp;
delete[] s.data;
s.len = strlen(temp.c_str());
s.data = new char[s.len+1];
strcpy(s.data, temp.c_str());
return is;
}
Cheated with std::string
- Actually, need to read each character from
stdin, and get whitespace right .c_strgives the regularchar*
operator[]()
Gives the i-th character
char&return, because need to write into the string
If MyString is const, both of the following would work:
const char& operator[](int i) const;
char& operator[](int i) const;
char& MyString::operator[](int i) {
if (i < 0 || i >= len) {
throw std::out_of_range{"MyString::op[]"};
}
return data[i];
}
operator[]() const
If this is const, all its members will be const
- Cast away
constness *thishas typeconst MyString*- When you dereference
const MyString*, it becomesconst MyString& - Cast to regular
MyString& - Can invoke the regular
operator[](), which returns achar& - Assigning
char&intoconst char&is fine
const char& MyString::operator[](int i) const {
// illustration of casting away constness
return ((MyString&)*this)[i];
}
The cpp syntax is:
return const_cast<MyString&>(*this)[i];
- Also checks if
*thisisMyString&to begin with
Exception Handling
throw std::out_of_range{"MyString::op[]"};
// same as
std::out_of:range ex{"MyString::op[]"}; // construct a temp object
throw ex // return by value
std::out_of_rangeis a type, constructs a temporary objectex, and returns by value
If not catched, and the exception goes out of main(), a library function will catch it, and terminate the program
try {
f1(); // function call that may throw an exception
}
catch (const out_of_range& e) {
cout << e.what() << endl;
}
Catch with const reference (if by value, has a copy construct…)
- Then invoke the member function
what()
If throwing an exception makes a function exit in the middle, the local variables will be properly destructed
