Размещение нового в std:: aligned_storage?
Предположим, что у меня есть шаблон шаблона типа T.
И пусть у меня есть std::aligned_storage
следующим образом:
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage;
Я хочу разместить новую a T в storage
.
Что соответствует стандартизованному значению/типу указателя для перехода к новому оператору размещения, и как мне получить это из storage
?
new (& ???) T(a,b,c);
Например:
new (&storage) T(a,b,c);
new (static_cast<void*>(&storage)) T(a,b,c);
new (reinterpret_cast<T*>(&storage)) T(a,b,c);
new (static_cast<T*>(static_cast<void*>(&storage));
Какое из вышеперечисленных (если есть) совместимо, а если нет, то какой способ лучше?
Ответы
Ответ 1
Самый параноидальный способ -
::new ((void *)::std::addressof(storage)) T(a, b, c);
Пояснение:
-
::std::addressof
защищает от перегруженного унарного operator&
на storage
, что технически разрешено стандартом. (Хотя никакая разумная реализация не сделала бы этого.) ::std
защищает от любых пространств имен (или классов) верхнего уровня, называемых std
, которые могут быть в области видимости.
-
(void *)
(который в этом случае является static_cast
), вы вызываете размещение operator new
с void *
, а не чем-то вроде decltype(storage) *
.
-
::new
пропускает любое размещение класса operator new
s, гарантируя, что вызов переходит к глобальному.
Вместе это гарантирует, что вызов переходит к размещению библиотеки operator new
с помощью void *
и что T
построен там, где storage
.
В большинстве здравомыслящих программ,
new (&storage) T(a,b,c);
должно быть достаточно.
Ответ 2
Функция распределения места размещения описывается следующим образом (С++ 14 n4140 18.6.1.3):
void* operator new(std::size_t size, void* ptr) noexcept;
Возвращает: ptr
.
Примечания: Преднамеренно не выполняет никаких других действий.
20.10.7.6 таблица 57 описывает aligned_storage<Len, Align>
таким образом:
Элемент typedef type
должен быть тип POD, подходящий для использования как неинициализированное хранилище для любого объекта размер которого не превышает Лен и чей alignment - это делитель Align.
Это означает, что в вашем случае &storage
подходит для размещения объекта типа T
. Поэтому при нормальных условиях 1 все 4 способа, которые вы указали для размещения вызовов new
, действительны и эквивалентны. Для краткости я использовал бы первый (new (&storage)
).
1 T.C. правильно указал в комментариях, что технически возможно, чтобы ваша программа объявила о перегрузке функции распределения, взяв typename std::aligned_storage<sizeof(T), alignof(T)>::type*
, которая затем будет выбрана с помощью разрешения перегрузки вместо предоставленной библиотекой "новой версии размещения".
Я бы сказал, что это маловероятно, по крайней мере, в 99,999% случаев, но если вам нужно также защититься от этого, используйте одну из приведений в void*
. Прямого static_cast<void*>(&storage)
достаточно.
Кроме того, если вы параноидально к этому уровню, вы должны использовать ::new
вместо просто new
, чтобы обойти любые функции выделения класса.