Class with dynamic memory
A string example
String class definition
class MyString {
    private:
        int buf_len;
        char * characters;
    public:
        MyString(int buf_len = 64, char *data = nullptr){
            this->buf_len = 0;
            this->characters = nullptr;
            create(buf_len, data); // this is a helper function, it will allocate memory and copy data
        }
        ~MyString(){
            delete[] characters;
        }
        // other member functions
};- Before a 
MyStringobject is used, the length of the string is not konwn.- As a result, the memory should be allocated dynamically.
 
 
The create function
    bool create(int buf_len,  const char * data)
    {
        this->buf_len = buf_len;
        if( this->buf_len != 0)
        {
            this->characters = new char[this->buf_len]{};
            if(data)
                strncpy(this->characters, data, this->buf_len);
        }
    
        return true;
    }In the main function, we can use the MyString class as follow:
int main()
{
    MyString str1(10, "stevens");
    cout << "str1: " << str1 << endl;
    MyString str2 = str1; 
    cout << "str2: " << str2 << endl;
    MyString str3;
    cout << "str3: " << str3 << endl;
    str3 = str1;
    cout << "str3: " << str3 << endl;
    return 0;
}- Why memory leak and memory double free?
 
Break down
Hard copy
Helper functions
release(): release the memorycreate(): first release the current memory, then allocate new memory and copy data
bool MyString::release(){
    this->buf_len = 0;
    if(this->characters!=nullptr){
        delete []this->characters;
        this->characters = nullptr;
    }
    return true;
}
bool MyString::create(int buf_len,  const char * data){
    release(); 
    this->buf_len = buf_len;
    if( this->buf_len != 0){
        this->characters = new char[this->buf_len]{};
    }
    if(data)
        strncpy(this->characters, data, this->buf_len);
    return true;
}Copy constructor & copy assignment operator
One reason causes memory leak and double free is that the copy constructor provided by the compiler is not appropriate.
MyString::MyString(const MyString & other){
    this->buf_len = 0;
    this->characters = nullptr;
    create(other.buf_len, other.characters);
}
MyString& MyString::operator=(const MyString & other){
    create(other.buf_len, other.characters);
    return *this;
}- Now each objects has its own memory.
 - It’s a hard copy.
 
Self-assignment
We should improve the copy assignment operator by checking if the source and the target are the same object.
- If the address of the source and the target are the same(self-assignment), do nothing.
- If not, it will release the current object’s memory, then you can’t find the data anymore!
 
 - Otherwise, release the current memory, then allocate new memory and copy data.
 
MyString& MyString::operator=(const MyString & other){
    if(this == &other)
        return *this;
    create(other.buf_len, other.characters);
    return *this;
}Soft copy
Problem of Hard copy
- Frequently allocate and deallocate memory.
 - Not efficient when the memory is large.
 
Soft copy helps to solve the problem. However,
- Multiple objects share the same memory.
 - How to decide when to release the memory?
 
Solution 1: Reference counting
In each MyString object, we maintain a reference count.
- The reference count is initialized to 1.
- The count will increase/decrease when the object is assigned or destroyed.
 - When the reference count is 0, the memory is released.
 
 - The reference count is stored in a shared part of the memory.
- We can use a integer on the heap to store the reference count.
 
 
Solution 2: a struct to store both the reference count and the memory
Since both the reference count and the memory are used on the heap
- we can use a 
structto store them together. 
=delete and =default
=deleteis used to disable a function.=defaultis used to enable a function that is defined by the compiler by default.
// in class definition
MyString(const MyString & other) = delete;
MyString& operator=(const MyString & other) = delete;
// main function
MyString str1 = "stevens";
MyString str2 = str1; // error
MyString str3;
str3 = str1; // error- Now the copy constructor and copy assignment operator are disabled.
 - You can’t use copy constructor and copy assignment operator.
 
Smart pointers
std::unique_ptr
Different from std::shared_ptr, std::unique_ptr is a unique owner of the memory.
- It does not allow copy assignment or copy constructor.
 - It can be moved to another 
std::unique_ptr, bystd::move(). - When the 
std::unique_ptrgoes out of scope, the memory is released. 
std::unique_ptr<MyTime> mt1 = std::make_unique<MyTime>(1, 70);
std::unique_ptr<MyTime> mt_test = mt1; // error
std::unique_ptr<MyTime> mt2 = std::move(mt1); // ok
cout << "mt1: " << *mt1 << endl; // segmentation fault
cout << "mt2: " << *mt2 << endl; // okstd::move()is used to transfer the ownership of the memory.- After the transfer, 
mt1loses the ownership of the memory, and it becomes a null pointer. 
Summary
Rule of three
If a class requires a user-defined destructor, copy constructor, or copy assignment operator, it likely requires all three.
- If all the data members are plain data types, no need to define your own.
 - If you handle the dynamic memory by yourself, you must define all three.
 
Alternatively,
- Smart pointers can help you manage the memory automatically.
 - You can use 
=deleteto disable the copy constructor and copy assignment operator. 
Why no many kinds of constructors in Java and Python?
- Programmers in Java and python deal with open files, sockets, database connections, etc.
 - These works are often connected to the memory management.
 - As a result, Java and Python simplify the memory management by using shared pointers.
- Every object except for primitive types is an object.
 - Every copy is a soft copy and object deallocation is handled by the reference count.
 
 - Therefore, C++, which focuses on system-level programming, algorithmic data structures, and high-performance computing as its main business, has developed these concepts.
 - It incorporates the following ideas as fundamental elements of the language:
- Copy/move operations
 - Pointers
 - Mutability
 - Multithreading
 
 - Copy/move operations
 - These concepts are extremely important in our operations, making them irreplaceable.
 
Recursion
Recursion is the technique of making a function call itself.
- Break down a problem into smaller problems of the same type.
 - Each recursive call should make the problem smaller.
 - There should be a base case that terminates the recursion.
 
int factorial(int n){
    if(n == 0)
        return 1;
    return n * factorial(n-1);
}- The base case is 
n == 0, which returns 1. - Each recursive call makes the problem smaller by 
n-1. - When 
nbecomes 0, the recursion terminates. 
Another example:
void div2(int n){
    cout << "Entering div2 with n = " << n << endl;
    if(n == 0)
        return;
    div2(n/2);
    cout << "Leaving div2 with n = " << n << endl;
}
int main(){
    div2(1024);
    return 0;
}- The video
 
Almost all recursive functions can be converted to iterative functions.
Pros of recursion:
- Good at tree traversal
 - Less lines of source code
 
Cons of recursion:
- Consume more stack memory
 - May be less efficient
 - Hard to implement and debug