Я хочу построить B
на месте A
.
struct A {
size_t x; // 8 bytes
int i; // 4 bytes
}; // padding 4 bytes
struct B {
size_t x; // 8 bytes
bool b; // 1 byte
}; // padding 7 bytes
int main() {
A a;
auto p = new(&a) B;
// auto p = new(std::launder(&a)) B; // is a bit better
}
Этот код четко определен? Будет ли placement new
касаться байтов заполнения (например, устанавливать все нули) или будет работать напрямую с b
байтами?
Например, две структуры A
хранятся рядом (сразу после первой A::i
идет вторая A::x
). Если placement new
коснется отступа, данные второго A
будут повреждены.
Выделение большего количества байтов, чем размер A, является неопределенным поведением. Заполнение зависит от настроек компилятора и платформы, поэтому в лучшем случае вы получите что-то, что работает на вашей машине. Поэтому я бы даже не осмелился полагаться на упомянутые вами значения заполнения (и предположение, что sizeof size_t и указатель равны). Особенно нет, если код должен быть переносимым.
Что меня действительно интересует, так это то, почему вы считаете хорошей идеей размещать новый объект в другой структуре данных вместо выделенного (относительно) большого буфера. Вы пытаетесь сделать какой-то каламбур/псевдоним?
@PepijnKramer это связано с решением stackoverflow.com/q/78365893/23545465
«For example, two A structures are stored contiguously (right after first A::i goes second A::st)
» Нет, это не так. A
независимо от того имеет отступы.
Здесь есть несколько вещей, которые стоит обсудить.
Во-первых, заполнение обычно не указано, поэтому не следует предполагать, что вы знаете, что и где находится. Основные ограничения заключаются в том, что
T[N]
имеет размер N * sizeof(T)
, т. е. нет «дополнительного заполнения массива». Это дает вам минимальные последствия заполнения для T
.Это означает, что ваше «сразу после того, как первый A::i
идет вторым A::st
» не может быть выполнено; это не то, что вам решать.
Размещение-новое может повторно использовать существующее хранилище, и основное требование состоит в том, чтобы хранилище было достаточно большим и соответствующим образом выровнено для типа назначения. Вы можете статически утверждать эти вещи с помощью sizeof
и alignof
. Результатом выражения Place-new является то, что целевой объект (типа B
в вашем примере) инициализируется (в соответствии с выбранным синтаксисом инициализации), который потенциально будет записывать в любое и все хранилище, занимаемое объектом B
. В соответствии с вышеуказанными требованиями они будут полностью храниться в предоставленном хранилище, или у вас уже есть UB.
Если вы хотите, чтобы целый массив A[N]
обеспечивал хранилище для некоторой последовательности объектов, то, конечно, в принципе возможно перезаписать хранилище нескольких соседних A
объектов, но при условии, что каждый B
объект будет заново сконструирован в один конкретный A
объект и что он предоставляет достаточно места для хранения (как описано выше), то ни один B
объект не сделает недействительными несвязанные базовые A
объекты.
Если вы не контролируете то, что «клиент» помещает в вашу структуру данных, все еще есть вещи, связанные с объединениями/вариантами и константностью, которые следует учитывать. И это может быть довольно сложно, прочитайте еще немного о std::launder: stackoverflow.com/questions/39382501/…. Мне бы очень хотелось знать, какие «требования клиента» побуждают вас отправиться в страну «здесь драконы» ;) Для меня это был бы один из тех случаев... давайте вернемся к клиенту и поговорим с ним еще раз.
Я не думаю, что можно трогать отступы, поскольку структура легко конструируется. Но формально это может быть УБ, а может и не быть, я не уверен (вероятно, на практике безвредно). Меня больше беспокоит несовпадение структур, но я думаю, что на большинстве платформ это тоже должно быть нормально?