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)
Далее следуют три вопроса:
- Это правильная цитата?
- Если да, то в какой момент срок службы
X
начать? Если на линии(B)
Это сам актерский состав, который считается приобретением хранилища? Если на линии(A)
что, если между(A)
а также(B)
что бы условно построитьX
или какой-то другой стручок,Y
? - Что-нибудь меняется в этом отношении между 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]):
неявно созданные объекты, адрес которых является адресом начала области хранения, и производят значение указателя, указывающее на этот объект, если это значение приведет к тому, что программа определит поведение [...]
Этот анализ основан на 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
и имеет точно такое же время жизни, независимо от того, создаете ли вы указатель (того же или преобразованного типа) на это хранилище или нет.