Обновить

Комментарии 2

Как вы думаете, объект какого типа неявно создастся?:

Неясно что вы называете "создаться". Формально оно уже создано, вопрос только как вы будете интерпретировать созданный объект. Вы можете хоть десять раз подряд кастить типы между собой и менять в соответсвии с полями - пока вы в пределах выбранной памяти можно писать и переписывать любые данные.
Конструирование/деструкция же типа это просто (де)инициализация полей в определённом порядке и ассоциирование некоторой логики после инициализации полей.

Так ваш первый "недостаточный" пример Foo выглядел бы так.

struct Foo{ 
  std::string name; 
  /* прочие поля */
  Foo(); 
};
Foo::Foo()
: name("foo")
{}

Единственная разница - при нехватке памяти malloc вернёт nullptr, а new Foo выкинет исключение bad_alloc. Ну и если мы хотим явно привязать какую-то логику после конструирования, то мы её просто инкапсулируем в пределах конструктора, в то время как в Си это делается посредством джентельменского соглашения или заворачивание этой логики в какой-нибудь foo_new() и foo_free(), что суть то же соглашение.

Обращение к неинициализированным полям в обоих ситуациях остаётся UB.

magic, version, flags - таким кодом тоже будут неявно созданы

опять же - нет. созданы они строчкой выше, на моменте где вы добыли себе байты. Объявление лайфтайма фактически тот же reinterpreter_cast, но семантически чище. Инициализации типов или выделений памяти на этом этапе не происходит. Отсюда и проблема с инициализациие лайфтаймов подобъектов.

Что делать? Я знаю только одну опцию:

ничего не делать, т.к. формально состояние созданного объекта уже ill-former уже после объявление начала времени жизни и дальнейшии манипуляции с ним становятся UB. Поэтому s->i = 3; тоже будет UB, т.к. необходимый инвариант уже нарушен. Если вы уверены в поведении вашего компилятора, то с этим в теории можно работать, но без гарантий, как и с любым UB.

К t1 мы нормально обращаться не можем, поэтому создаем через std::launder новый указатель t2 с provenance, отсылающим к объекту w->t

картинка врёт. t2 после std::lauder не будет иметь отдельного объекта и должно ссылаться на начало Wrapper.

В остальном - отличный ликбез

Единственная разница - при нехватке памяти malloc вернёт nullptr, а new Foo выкинет исключение bad_alloc. 

Чуть напомню, что можно собрать код без исключений или взывать отедльный noexecpt new и тогда также вернется nullptr при выделении памяти

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации