Question:
I read about aligned_storage()
on cppreference , but I couldn't figure it out. Please explain in a simpler way.
Answer:
Let's break down the article you specified in paragraphs.
Provides the member typedef type, which is a
PODType
…
The std::aligned_storage
wrapper std::aligned_storage
is a primitive type, that is, data is stored in it in a contiguous heap of bytes (like a structure in C), without C ++ problems. Thanks to this, it can be easily copied even through memcpy
.
… suitable for use as uninitialized storage …
Since the wrapper is a primitive type, it has no constructor or destructor. Accordingly, the constructor of the wrapped type will not be called either. As a result, we can only wrap primitive types (alas).
… for any object whose size is at most
Len
and whose alignment requirement is a divisor ofAlign
.
The wrapped type must 1) fit completely into the wrapper and 2) its valid alignment (multiplicity of the address in RAM) must be the divisor of Align
.
Let's take the following example. Let Align = 16
. Its divisors are 1, 2, 4, 8 and 16. This means that we can put in this container:
-
char
/uint8_t
/int8_t
(alignment – 1 byte), -
uint16_t
/int16_t
(alignment – 2 bytes), -
uint32_t
/int32_t
(alignment – 4 bytes), -
uint64_t
/int64_t
(alignment – 8 bytes), - or an SSE vector represented as
uint8_t[16]
(expected alignment – 16 bytes).
Why was alignment introduced? The processor exchanges with RAM not bytes, but blocks of a fixed size 2 n for some integer n . If the variable is aligned, then it can be transferred in the least possible number of hops. Unaligned data can cross the block boundary and require one more transfer.
In addition, some operations (fast aligned loading commands in SSE, for example) generally throw out a processor interrupt if there is no alignment to a certain boundary (for SSE – 16 bytes).
It's worth noting that alignment is equal to size only for primitive types. Composite types (structures and unions) have the same alignment as the largest field. This ensures that the longest field is aligned in size. Well, since the sizes of primitive types are equal to powers of two (that is, the alignment of large types is a multiple of the alignment of types of smaller ones), then all other fields will be aligned.
The default value of
Align
is the most stringent (the largest) alignment requirement for any object whose size is at mostLen
.
Align
can be omitted. Then the compiler will align as it would align the closest (but not larger) type. That is, as if we wrote std::aligned_storage<sizeof(T), alignof(T)>
, where T
is some built-in (albeit not existing) type with the size we need.
If the default value is not used,
Align
must be the value ofalignof(T)
for some typeT
, or the behavior is undefined.
The third piece of text listed the types allowed for placement in a particular specialization of a wrapper with a specific size and alignment. The same paragraph imposes a restriction on the specific selected type : Align
must have a typical alignment for this type. That is, we cannot place uint16_t
with Align=6
, assuming that 6 and 3 are valid alignment values - this provokes undefined behavior (an example was already given about alignment for SSE above).
But this fragment also prohibits setting Align
, which is a multiple of the real alignment. For example, we cannot accommodate a uint16_t
aligned to 4, 8, 12, etc. bytes.
And yes, we can't align an SSE vector if we don't have a ready-made, 16-byte type. After all, then we are forced to use char[16]
, whose native alignment is 1 byte ( char
type is char
). We need 16 – hello undefined behavior? And if we had a ready-made type, the compiler would align it by its own size (which coincides with the required alignment), and we don't really need std::aligned_storage
.
The behavior is undefined if
Len == 0
.
The wrapper must have a nonzero length (otherwise nothing will fit in it).