In addition to Ben Voigt's answer which details when it is OK to omit the destructor call, it is important to ensure the memory is properly aligned for the type to be placed-new in it. I'll try to write it up here as requested by to OP.
This line:
S *newS = new(buffer + offset)S;
works only if the address buffer + offset
is aligned properly:
3.11 Alignment
1 Object types have alignment requirements (3.9.1, 3.9.2) which place restrictions on the addresses
at which an object of that type may be allocated. An alignment is an
implementation-defined integer value representing the number of bytes
between successive addresses at which a given object can be allocated.
[...]
buffer
itself is properly aligned for any type with fundamental alignment requirement:
3.7.4.1 Allocation functions
2 [...]
The pointer returned shall
be suitably aligned so that it can be converted to a pointer of any
complete object type with a fundamental alignment requirement (3.11)
and then used to access the object or array in the storage allocated
[...]
To know the alignment requirement of a type, there is alignof(type)
. Then there is std::max_align_t
, alignof(std::max_align_t)
returns the greatest alignment value of all types with fundamental alignment requirement.
There is a special case of types that require an extended alignment, to be sure your type is not one of these I would include this in your program:
static_assert(alignof(S) <= alignof(std::max_align_t),
"Extended alignment required for S");
Then, you just have to make sure that offset
is a multiple of alignof(S)
.