Let's start with delete
When you delete
an object there are two things that happen:
- the destructor of that's object is called
- the memory allocated for this object with
new
is being released back to the heap
placement new
and delete
You can use placement new
syntax for constructing an object on an existing memory buffer:
const char* charString = "Hello, World";
// allocate the required memory
void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
// construct a "Buffer" object on an existing memory block
Buffer* buf = new(mem) Buffer(strlen(charString));
// ...
// destruct the "Buffer" object without releasing the memory
buf->~Buffer();
// deallocate the memory
::operator delete(mem);
Of course for this example you could just use plain new
and delete
but it shows how you can separate the memory allocation from its construction and the destruction from the deallocation.
This technique is useful for example if you manage memory pool and the memory can be reclaimed back to the memory pool when the object is destructed rather than back to the heap.
std::allocator
std::allocator
gives you the above behavior - separation of memory allocation and deallocation from the object construction and destruction with the methods allocate
, construct
, destroy
and deallocate
.
allocator<string> alloc;
size_t n = 3;
auto p = alloc.allocate(n); // memory is allocated for n elements
alloc.construct(p); // object is constructed on that memory
// ...
alloc.destroy(p); // object is destructed
alloc.deallocate(p, n); // memory is deallocated
Note that std::string
destructor would call delete
on its internal allocation when the string
object is destructed, but the memory occupied by the object itself is deallocated in above code only in the call to alloc.deallocate
.
construct
and destroy
being deprecated in C++17
C++17 declared the methods construct
and destroy
of std::allocator
deprecated and C++20 made them obsolete. So, since C++17 you would have to either go back to placement new
, or better (thanks @Evg for the comment) - use std::allocator_traits::construct() which has the benefit of being constexpr
since C++20. For the destruction, call the destructor directly after deallocating the memory with the allocator:
allocator<string> alloc;
size_t n = 3;
auto p = alloc.allocate(n); // memory is allocated for n elements
// alloc.construct(p); // deprecated in C++17, obsolete in C++20
// option a:
// new(p) string; // construct the object with placement new
// option b, better - (potentially) constexpr since C++20:
std::allocator_traits<allocator<string>>::construct(alloc, p, "hello");
// ...
// alloc.destroy(p); // deprecated in C++17, obsolete in C++20
p->~string(); // destruct the object by calling the destructor
alloc.deallocate(p, n); // memory is deallocated
Code link
construct_at
and destroy_at
*
* since C++20 and C++17, respectively.
With construct_at
and destroy_at
the code above can be written as:
allocator<string> alloc;
size_t n = 3;
auto p = alloc.allocate(n); // memory is allocated for n elements
std::construct_at(p, "hello"); // added in C++20
// ...
std::destroy_at(p); // destruct the object - added in C++17
alloc.deallocate(p, n); // memory is deallocated
This one is the simplest, if you are in C++20 I'd recommend this option.
Code link
Last note: there is also a thing called placement delete
it is not called directly by the programmer but only from placement new
if the constructor of the object throws an exception. So don't get confused with that. The way to destruct an object in place without releasing the memory occupied by the object itself is by calling its destructor directly as shown above (or if you use std::allocator prior to C++17, by calling the allocator destroy method).
Last and final note: if you wonder why deallocate
require a size? see: why does std::allocator::deallocate require a size?