Question:
I read about aligned_storage()
on cppreference , but I couldn't figure it out. Explain, please, more simply.
Answer:
Let's break it down paragraph by paragraph.
Provides the member typedef type, which is a
PODType
…
The wrapper std::aligned_storage
is a primitive type, that is, data is stored in it as a continuous bunch of bytes (like a structure in C), without C++ troubles. Thanks to this, it can be safely copied even through memcpy
.
…suitable for use as uninitialized storage…
Since the wrapper is a primitive type, it has neither a constructor nor a destructor. Accordingly, the wrapper type's constructor will also not be called. 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 type being wrapped must 1) fit entirely within the wrapper, and 2) its valid alignment (a multiple of the address in RAM) must be a 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 is 16 bytes).
Why was alignment introduced? The processor exchanges with the RAM not bytes, but by blocks of a fixed size 2 n with a certain integer n . If a variable is aligned, then it can be transferred in the least possible number of transfers. Unaligned data may cross a block boundary and require one more transfer.
In addition, some operations (fast aligned loading commands in SSE, for example) generally throw 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. For composite types (structures and unions), the alignment is equal to that of the largest field. This ensures that the longest field is justified in size. Well, since the sizes of primitive types are equal to a power of two (that is, the alignment of large types is a multiple of the alignment of smaller types), 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
may or may not be specified. The compiler will then 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 to be placed in a particular wrapper specialization at a particular size and alignment. The same paragraph imposes a constraint on the particular type chosen : Align
must have a typical alignment for that type. That is, we cannot place a uint16_t
with Align=6
, assuming that 6 and 3 are valid alignment values - this provokes undefined behavior (an example about alignment for SSE was already given above).
But this snippet also disables setting Align
to a multiple of the actual alignment. For example, we cannot allocate uint16_t
with 4, 8, 12, etc. alignment.
And yes, we can't flatten 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 (because the type is char
). We need 16 – hello, undefined behavior? And if we have a ready-made type, the compiler itself will align it to its own size (which coincides with the required alignment), and we don’t need std::aligned_storage
.
The behavior is undefined if
Len == 0
.
The wrapper must have a non-zero length (otherwise nothing will fit in it).