Размещение нового в 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, чтобы обойти любые функции выделения класса.