It’s really, really simple: new
can be thought of as doing two things:
- Allocating the memory.
- Placement-constructing the object in the allocated memory.
There’s no guarantee that malloc
is actually used by the implementation, but typically it is. You cannot assume it about the implementation, but for the purpose of understanding it’s an OK assumption.
Thus, the following are thought of as being equivalent:
auto obj1 = new std::string("1");
// ↑ can be thought of as equivalent to ↓
auto obj2 = (std::string*)malloc(sizeof(std::string));
new(obj2) std::string("2");
Same goes for delete
:
delete obj1;
// ↑ can be thought of as equivalent to ↓
obj2->~string();
free(obj2);
You can then easily reason about it all when you see new
and delete
for what they really are: an allocation followed by constructor call, and a destructor call followed by deallocation.
When you use placement new
, you’ve decided to take care of the first step separately. The memory has to be still allocated somehow, you just get to have full control over how it happens and where does the memory come from.
You thus must keep track of two things, separately:
-
The lifetime of the memory.
-
The lifetime of the object.
The code below demonstrates how these are independent of each other:
#include <cstdlib>
#include <string>
#include <new>
using std::string;
int main() {
auto obj = (string*)malloc(sizeof(string)); // memory is allocated
new(obj) string("1"); // string("1") is constructed
obj->~string (); // string("1") is destructed
new(obj) string("2"); // string("2") is constructed
obj->~string (); // string("2") is destructed
free(obj); // memory is deallocated
}
Your program has UB if the lifetime of the object extends past the lifetime of memory. Make sure that the memory always outlives the life of the object. For example, this has UB:
void ub() {
alignas(string) char buf[sizeof(string)]; // memory is allocated
new(buf) string("1"); // string("1") is constructed
} // memory is deallocated but string("1") outlives the memory!
But this is OK:
void ub() {
alignas(string) char buf[sizeof(string)]; // memory is allocated
new(buf) string("1"); // string("1") is constructed
buf->~string(); // string("1") is destructed
} // memory is deallocated
Note how you need to properly align the automatic buffer using alignas
. The lack of alignas
for an arbitrary type results in UB. It might appear to work, but that’s only to mislead you.
There are some specific types where not calling the destructor and not aligning the memory properly does not lead to UB, but you should never assume such things about a type. Call your destructors and do the alignment, it won’t cost you anything if it turns out to be unnecessary – no extra code would be generated for such a type.
struct S {
char str[10];
}