Во-первых, директивы #pragma
не являются переносимыми. Стандарт не определяет никаких обязательных прагм, которые должны поддерживаться, поэтому каждый компилятор может определять свой собственный набор. Но std::aligned_storage
требуется, чтобы просто работать независимо от того, какой компилятор вы используете. Авторы библиотек компиляторов могут определять его в терминах прагм, атрибутов или расширений компилятора, но пользователь может просто #include <type_traits>
и начать использовать его.
И это не правда, что "это можно использовать только с типами POD". Фактически, одним из распространенных способов использования aligned_storage
является использование фрагмента памяти, в котором другие объекты типа любого типа могут быть созданы и уничтожены вручную. Это или что-то подобное можно использовать для реализации таких вещей, как std::optional
и std::variant
.
Чтобы показать идею, стоящую за этим, вот начало написания класса, подобного std::optional
:
#include <type_traits>
#include <memory>
template <typename T>
class my_optional
{
private:
std::aligned_storage_t<sizeof(T), alignof(T)> m_storage;
bool m_valid;
public:
constexpr my_optional() noexcept : m_valid(false) {}
constexpr my_optional(const T& obj)
noexcept(std::is_nothrow_copy_constructible<T>::value)
: m_valid(false)
{
new(static_cast<void*>(&m_storage)) T(obj);
m_valid = true;
}
constexpr const T& operator*() const
{
return *static_cast<const T*>(static_cast<const void*>(&m_storage));
}
constexpr T& operator*()
{
return *static_cast<T*>(static_cast<void*>(&m_storage));
}
~my_optional()
{
if (m_valid)
operator*().~T();
}
// Much more, to obey the Rule Of Five and add more functionality...
};