You have been briefly introduced to new-expressions and delete-expressions in "Storage Durations", these expressions deal with objects located in dynamic storage. You should know that a new-expression creates an object that has dynamic storage duration and a delete-expression destroys an object that was created with a new-experssion. The primary reason to use new and delete is for implementing a container or data structure such as: std::vector, std::map, and std::list.
Let's dive deeper into what a new-expression is actually doing. A new-expression creates an object first by allocating storage for the object and then by initializing that storage. The storage that an object occupies is composed of a contiguous sequence of bytes; the unique address of the first byte of an object's storage is the memory location of that object.
All types of storage in C++ are composed of a contiguous sequence of bytes with each byte having a unique address (even across different types of storage). The particular type of storage that a new-expression allocates is dynamic storage, but the new-expression does not do this directly. The new-expression delegates the task of allocating dynamic storage to the global allocation functions: ::operator new and ::operator new[].
The global allocation functions only allocate dynamic storage; unlike the new-expressions, they do not initialize the dynamic storage that they allocate. To prove this, you may run the following program:
#include <iostream>
using namespace std;
struct C {
C()
{
cout << '[' << this << "] C::C()" << endl;
}
~C()
{
cout << '[' << this << "] C::~C()" << endl;
}
};
int main()
{
C* x1 = ::new C;
cout << "x1 = " << x1 << endl;
::delete x1;
C* x2 = (C*)::operator new(sizeof(C));
cout << "x2 = " << x2 << endl;
::operator delete(x2);
return 0;
}
The output will be something like:
[0x562aff849e70] C::C()
x1 = 0x562aff849e70
[0x562aff849e70] C::~C()
x2 = 0x562aff849e70
You can see here that the constructor for C was only called during the new-expression; ::operator new did not call the constructor for C. The delete-expression called the destructor for C, but the global deallocation function ::operator delete did not.
Also note that :: was used in front of the new and delete-expressions, it is possible to override the allocation and deallocation functions that new and delete-expressions use for a particular type; by explicitly using ::new and ::delete, the new and delete-expressions will use the global allocation functions provided by C++.
If you ever need to allocate storage without initializating it, consider using the global allocation and deallocation functions instead of the new and delete-expressions.
To allocate an uninitialized array, the ::operator new[] function should be used:
int* p = (int*)::operator new[](100 * sizeof(int));
The code above allocates an uninitialized array of 100 int and stores a pointer to this array in p. To deallocate an array that was allocated with ::operator new[], you must call ::operator delete[] like: ::operator delete[](p).
It is possible to manually initialize an object, whether or not it has already been initialized:
#include <iostream>
using namespace std;
int main()
{
int x = 42;
cout << "x: " << x << endl;
::new(&x) int;
cout << "x after default-initialization: " << x << endl;
::new(&x) int();
cout << "x after value-initialization: " << x << endl;
return 0;
}
The output of the above program is:
x: 42
x after default-initialization: 42
x after value-initialization: 0
The new-expression used in the form of ::new(&x) int; default-initializes an already allocated object located at &x. ::new(&x) int(); value-initializes an already allocated object located at &x.
The output of this program shows that default-initialization does nothing for non-class types while value-initialization zero-initializes a non-class-type object by setting all the bytes in that object's storage to zero.
The following program demonstrates the manual allocation, construction, destruction, and deallocation of a class-type object:
#include <iostream>
using namespace std;
struct T {
int x;
T(int x)
{
cout << "T::T()" << endl;
this->x = x;
}
~T()
{
cout << "T::~T()" << endl;
}
};
int main()
{
T* p = (T*)::operator new(sizeof(T));
::new(p) T(42);
cout << "p->x: " << p->x << endl;
p->T::~T();
::operator delete(p);
return 0;
}
The code in the main function does the same as if the code was:
T* p = ::new T(42);
cout << "p->x: " << p->x << endl;
::delete p;
The statement ::new(p) T(42) direct-initializes an object pointed to by p with the value 42. Since the object is a class-type, it's constructor will be called with 42 as the argument.
The statement p->T::~T() calls the destructor of an object of type T through it's pointer p; a statement like this can only be used on class-type objects unless you are writing code for a template.
# | Expression | Meaning |
---|---|---|
1 | (T*)::operator new(sizeof(T)) | Allocates, from dynamic storage, an uninitialized object of type T and returns a pointer to it |
2 | (T*)::operator new[](N * sizeof(T)) | Allocates, from dynamic storage, an array of N uninitialized objects of type T and returns a pointer to the first element. |
3 | ::new(p) T | Default-initializes an object of type T pointed to by p. For class-types, the constructor gets called; for non-class-types, nothing happens |
4 | ::new(p) T() | Value-initializes an object of type T pointed to by p. For class-types, the constructor gets called; for non-class-types, the object gets zero-initialized. |
5 | ::new(p) T(x) | Direct-initializes an object of type T pointed to by p. For class-types, the appropriate constructor gets called; for non-class-types, the value gets assigned after any necessary conversion. |
6 | ::new T | Performs expressions 1 and 3 |
7 | ::new T() | Performs expressions 1 and 4 |
8 | ::new T(x) | Performs expressions 1 and 5 |
9 | ::new T[N] | Performs expression 2, then performs expression 3 on each element |
10 | ::new T[N]() | Performs expression 2, then performs expression 4 on each element |
11 | p->T::~T() | Calls the destructor of an object, of type T, pointed to by p. |
12 | ::operator delete(p) | Deallocates the object pointed to by p which was previously allocated using expression 1. |
13 | ::operator delete[](p) | Deallocates the array pointed to by p which was previously allocated using expression 2. |
14 | ::delete p | If the object pointed to by p has a destructor, performs expression 11; then performs expression 12 |
15 | ::delete[] p | If the elements in the array pointed to by p have destructors, performs expression 11 on each element; then performs expression 13. |