C++ memory management: Part 3, behavioral splitting of new and delete

New and delete are both keywords of C++ and cannot be overloaded. Its underlying behavior can be viewed as a combination of multiple functions.

1. Implement the new and delete functions yourself

#include <iostream>
using namespace std;

class Student{<!-- -->
private:
    int age{<!-- -->24};
public:
    Student(){<!-- -->
        cout<<"start"<<endl;
    }

    ~Student(){<!-- -->
        cout<<"end"<<endl;
    }

    void f(){<!-- -->
        cout<<"age = "<<age<<endl;
    }
};

int main(void) {<!-- -->
    Student * p = (Student *)operator new(sizeof(Student)); //Implement new by yourself
    new(p) Student;

    p->f();

    p->~Student(); //Implement delete by yourself
    operator delete(p);

    return 0;
}

first row:
Student * p = (Student *)operator new(sizeof(Student));
Operator new is a function that comes with C++ and can be overloaded. The exact calling method is:
::operator new(sizeof(Student));
:: represents the global namespace, note that it is not the std::standard namespace!
The underlying call is the malloc function, which actually returns a void * pointer. The parameter indicates the number of bytes to be requested.

second line:
new§ Student;
Indicates that the constructor is executed at the given address (heap address).

Corresponding operations to delete:
p->~Student(); means executing the destructor at a certain address.
operator delete§;
What is called is the function that comes with C++, which can also be overloaded. The bottom layer calls the free() function.

2. Overloading of operator new and operator delete

Operator new and operator delete are two functions in the global space. The former is used to apply for space, and the latter is used to release space. The behavior of operator new can be broken down into three steps. The first step is to call the operator new() function to apply for memory space. For the operator new() function, we can overload our own version.
The operator new() function overloading rules are as follows:
(1) You can overload the global version, but it is not recommended and will affect the new operation of all classes. A version of a class can be overloaded. When overloading, just define operator new as a member function. The operator new function is a static function by default. Even if you do not add static, the compiler will set it as a static function. Because the function of operator new is to construct objects, generally speaking, the object has not been constructed when it is called. If it is a non-static function, it will not be available.
(2) The standard format of operator new is void* operator new(size_t size);
Multiple parameters can also be defined when overloading, but the first parameter must be size_t size.
(3) When the user performs a new operation on this class, the operator new function will be automatically called. Highlights:
Generally new does not take parameters, but if the overloaded operator new version has multiple parameters, other parameters besides size_t size need to be passed in new. For example:
void* operator new(size_t size, int test);
Then when using new: new(123) ClassName; 123 is the value passed to the formal parameter test.
(4) The system comes with two overloaded operator new functions. If we overload a custom version in the class, both of them will be disabled! ! ! When we do new, we will only call our custom operator new function.

Look at the code:

class Foo {<!-- -->
public:
    void* operator new(std::size_t size)
    {<!-- -->
        cout<<size<<endl;
        std::cout << "operator new" << std::endl;
        return std::malloc(size);
    }

    void* operator new[](std::size_t size)
    {<!-- -->
        cout<<size<<endl;
        std::cout << "operator new []" << std::endl;
        return std::malloc(size);
    }

    void operator delete (void * ptr){<!-- -->
        cout<<"operator delete"<<endl;
        free(ptr);
    }

    void operator delete[] (void * ptr){<!-- -->
        cout<<"operator delete []"<<endl;
        free(ptr);
    }

    long long a=1;
};

int main()
{<!-- -->
    Foo *px = new Foo;
    delete px;

    Foo * ps = new Foo[3];
    delete []ps;
}

operation result:

8
operator new
operator delete
twenty four
operator new []
operator delete []

We can also overload operator delete, operator new[], operator delete []

3. Multi-version overloading of operator new

Operator new can be overloaded in multiple versions, but note that the first parameter of all versions must be an unsigned integer! When we use the new keyword, we cannot pass in the first parameter, but start passing in the second parameter. Look at the code:

class Foo {<!-- -->
public:
    void* operator new(std::size_t size)
    {<!-- -->
        cout<<size<<endl;
        std::cout << "operator new" << std::endl;
        return std::malloc(size);
    }

    void* operator new(std::size_t size, int test)
    {<!-- -->
        cout<<"size = "<<size<<" test = "<<test<<endl;
        std::cout << "operator new" << std::endl;
        return std::malloc(size);
    }

    void* operator new(std::size_t size, string test)
    {<!-- -->
        cout<<"size = "<<size<<" test = "<<test<<endl;
        std::cout << "operator new" << std::endl;
        return std::malloc(size);
    }

    long long a=1;
};

int main()
{<!-- -->
    Foo *p1 = new(123) Foo; //void* operator new(std::size_t size, int test) is called
    delete p1;

    Foo *p2 = new("name") Foo; //void* operator new(std::size_t size, string test) is called
    delete p2;
}

operation result:

size = 8 test = 123
operator new
size = 8 test = name
operator new

This feature is very, very useful and can explain Placement new very well.

4. Placement new

Placement new, as the name suggests, is to create a new object at a specified location. placement new is a standard, global version of overloaded operator new.
Please remember one sentence: Placement new is the operator new function! ! !
The function is defined as follows:

_GLIBCXX_NODISCARD inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{<!-- --> return __p; }

The first parameter size_t is completely ignored, and there is no malloc process at all. The upper layer passes in a pointer and returns the pointer unchanged. The following behavior is like this: the compiler executes the constructor at this address based on the pointer returned by operator new, thereby achieving the purpose of fixed-point new. This pointer does not necessarily point to the heap, it can also point to the stack! ! !

Advantages of Placement new

(1) Use placement new to avoid frequent memory applications. A piece of memory can be used multiple times.

(2) It can improve the efficiency of constructing objects. If there is insufficient space in the heap, the object can be constructed in a buffer we prepared in advance.

(3) Used with shared memory, objects can be transferred between processes! ! ! This is simply a great artifact of IPC! ! !

5. Use of Placement new

Look at the code:

class Foo {<!-- -->
public:
    Foo(){<!-- -->
        cout<<"start!!!"<<endl;
    }

    ~Foo(){<!-- -->
        cout<<"end!!!"<<endl;
    }
    long long a=123;
};

int main()
{<!-- -->
    char * buff = new char [sizeof(Foo)]; // Fixed point new on the heap
    Foo *p1 = new(buff) Foo;
    cout<<"a = "<<p1->a<<endl;
    //delete p1; There is a risk that this piece of memory is not actually occupied by this object.
    p1->~Foo();

    delete [] buff;
    cout<<endl;


    char buf[sizeof(Foo)]; // Fixed point new on the stack
    Foo *p2 = new(buf) Foo;
    cout<<"a = "<<p2->a<<endl;
    p2->~Foo();

    return 0;
}

The usage steps are as follows:
(1) Apply for memory. This memory may be on the heap or on the stack.
(2) Set new at the given address.
(3) Use this object.
(4) Execute the destructor.

Points to note are as follows:
(1) When using Placement new, it is best not to overload the operator new function yourself, because once you have your own overloaded version, the standard library version will be disabled. Of course, it is also possible if you overload a function that is exactly the same as the inline void* operator new(std::size_t, void* __p); function in the standard library.
(2) Users are not allowed to call the constructor themselves, but they are allowed to call the destructor themselves.
(3) After using the fixed-point constructed object, do not delete it directly, because this memory may be used to construct other objects, and the destructor can only be called manually.