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 newis 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?