reinterpret_cast создание тривиально конструируемого объекта по умолчанию

cppreference утверждает, что:

Объекты с тривиальными конструкторами по умолчанию могут быть созданы с помощью reinterpret_cast в любом надлежащим образом выровненном хранилище, например в памяти, выделенной std::malloc,

Это подразумевает, что следующее является четко определенным кодом:

struct X { int x; };
alignas(X) char buffer[sizeof(X)];    // (A)
reinterpret_cast<X*>(buffer)->x = 42; // (B)

Далее следуют три вопроса:

  1. Это правильная цитата?
  2. Если да, то в какой момент срок службы X начать? Если на линии (B)Это сам актерский состав, который считается приобретением хранилища? Если на линии (A)что, если между (A) а также (B) что бы условно построить X или какой-то другой стручок, Y?
  3. Что-нибудь меняется в этом отношении между C++11 и C++1z?

Обратите внимание, что это старая ссылка. Формулировка была изменена в ответ на этот вопрос. Теперь оно гласит:

Однако, в отличие от C, объекты с тривиальными конструкторами по умолчанию не могут быть созданы путем простой интерпретации надлежащим образом выровненного хранилища, такого как память, выделенная std::malloc: location-new требуется, чтобы формально представить новый объект и избежать потенциально неопределенного поведения.

2 ответа

Решение

Здесь нет X объект, живой или другой, притворяющийся таким, что он есть, приводит к неопределенному поведению.

[intro.object] / 1 исчерпывающе разъясняет, когда создаются объекты:

Объект создается по определению ([basic.def]), выражению new ([expr.new]), при неявном изменении активного члена объединения ([class.union]) или при временном объекте создается ([conv.rval], [class.teven]).

С принятием P0137R1 этот абзац является определением термина "объект".

Есть ли определение X объект? Нет. Есть ли новое выражение? Нет. Есть ли союз? Нет. Есть ли в вашем коде языковая конструкция, которая создает временную X объект? Нет.

Все, что [basic.life] говорит о времени жизни объекта с пустой инициализацией, не имеет значения. Для того, чтобы это применить, вы должны иметь объект в первую очередь. Вы не

C++11 имеет примерно такой же абзац, но не использует его в качестве определения "объекта". Тем не менее, интерпретация одинакова. Альтернативная интерпретация - трактовать [basic.life] как создание объекта, как только будет получено подходящее хранилище, - означает, что вы создаете объекты Шредингера *, что противоречит N3337 [intro.object] / 6:

Два объекта, которые не являются битовыми полями, могут иметь один и тот же адрес, если один является подобъектом другого, или если хотя бы один является подобъектом базового класса нулевого размера, и они имеют разные типы; в противном случае они должны иметь разные адреса.


* Хранение с правильным выравниванием и размером для типа T по определению хранилище с надлежащим выравниванием и размером для каждого другого типа, размер и требования выравнивания которого равны или меньше, чем у T, Таким образом, эта интерпретация означает, что получение хранилища одновременно создает бесконечное множество объектов с разными типами в указанном хранилище, причем все имеют один и тот же адрес.

Основываясь на p0593r6, я считаю, что код в OP действителен и должен быть четко определен. Новая формулировка, основанная на DR, ретроактивно примененном ко всем версиям, начиная с C++98 включительно, позволяет неявно создавать объект, пока созданный объект хорошо определен (тавтология иногда спасает сложные определения), см. § 6.7.2.11 Объект модель [intro.object]):

неявно созданные объекты, адрес которых является адресом начала области хранения, и производят значение указателя, указывающее на этот объект, если это значение приведет к тому, что программа определит поведение [...]

См. Также: /questions/1873210/zakonno-li-obhodit-konstruktor-klassa-ili-eto-privodit-k-neopredelennomu-povedeniyu/55468247#55468247

Этот анализ основан на n4567 и использует номера разделов из него.

§5.2.10 / 7: Когда prvalue v типа указателя объекта преобразуется в указатель типа объекта "указатель на cv T", результат static_cast<cv T*>(static_cast<cv void*>(v)),

Итак, в этом случае reinterpret_cast<X*>(buffer) такой же как static_cast<X *>(static_cast<void *>(buffer)), Это заставляет нас взглянуть на соответствующие части о static_cast:

§5.2.9 / 13: значение типа "указатель на cv1 void" может быть преобразовано в значение типа "указатель на cv2 T", где T - тип объекта, а cv2 - та же квалификация cv, что и выше cv-квалификация, чем, cv1. Значение нулевого указателя преобразуется в значение нулевого указателя типа назначения. Если исходное значение указателя представляет адрес A байта в памяти и A удовлетворяет требованию выравнивания Tто результирующее значение указателя представляет тот же адрес, что и исходное значение указателя, то есть A,

Я полагаю, этого достаточно, чтобы сказать, что оригинальная цитата в некотором роде правильна - это преобразование дает определенные результаты.

Что касается жизни, это зависит от того, о какой жизни ты говоришь. Приведение создает новый объект типа указателя - временный, который имеет время жизни, начиная со строки, где находится приведение, и заканчивая всякий раз, когда оно выходит из области видимости. Если у вас есть два разных преобразования, которые выполняются условно, у каждого указателя есть время жизни, которое начинается с расположения созданного им приведения.

Ни один из них не влияет на время жизни объекта, предоставляющего основное хранилище, которое все еще bufferи имеет точно такое же время жизни, независимо от того, создаете ли вы указатель (того же или преобразованного типа) на это хранилище или нет.

Другие вопросы по тегам