#c #language-lawyer #pointer-arithmetic #placement-new
Вопрос:
В их примере из std::uninitialized_default_construct
:
struct S { std::string m{ "Default value" }; };
constexpr int n {3};
alignas(alignof(S)) unsigned char mem[n * sizeof(S)];
try
{
auto first {reinterpret_cast<S*>(mem)};
auto last {first n}; // (1)**********
std::uninitialized_default_construct(first, last);
// (2)**********
for (auto it {first}; it != last; it) {
std::cout << it->m << 'n';
}
std::destroy(first, last);
}
catch(...)
{
std::cout << "Exception!n";
}
// Notice that for "trivial types" the uninitialized_default_construct
// generally does not zero-fill the given uninitialized memory area.
int v[] { 1, 2, 3, 4 };
const int original[] { 1, 2, 3, 4 };
std::uninitialized_default_construct(std::begin(v), std::end(v));
// (3)**********
// for (const int i : v) { std::cout << i << ' '; }
// Maybe undefined behavior, pending CWG 1997.
У меня есть три вопроса. (Отметьте как ********** )
- Поскольку
S
иunsigned char
не являются взаимозаменяемыми указателями, будетlast
указывать на конец прошлогоmem
? Является ли арифметика указателей здесь неопределенным поведением? - Требуется ли здесь std::отмывание, чтобы сделать
first
иlast
на самом деле указатьS
? напримерfirst = std::launder(first); last = first n;
- Действительно ли это неопределенное поведение для печати элементов
v
? CWG 1997 говорит оunsigned char[]
хранении, но приведенный выше пример инициализированv
сint
помощью s.
Ответ №1:
- Это
unsigned char
еще один способ сказать1 byte
, поэтому распределение выполняется для размераstruct S
(в байтах), умноженного на количество элементовn
. Этогоmem
типаunsigned char []
, поэтому, согласно [усл.массив]/1 это будет эквивалентunsigned char *
, который указывает на первый элемент в массиве, и как [выражение.переиначить.литые]/4 Вreinterpret_cast
будет преобразовать его вstruct S*
что вызоветfirst
, которая объявлена какauto
быть создан в качествеstruct S*
, поэтому заявление является взаимозаменяемым сC
форма:struct S* first = (struct S*) amp;mem
Поскольку параметр
first
разрешен какstruct S*
, следовательноlast
, он также будет разрешен для того же типа, и арифметика указателей будет работать нормально. - В этом нет необходимости
std::launder
, так как операторauto first {reinterpret_cast<S*>(mem)};
уже инициализировал указатель на первый элементmem
, а следующий оператор с арифметикой указателя уже установил значениеlast
на 3-й элемент. - Я думаю, что это правильно, что это неопределенное поведение по состоянию на CWG 1997. Тот
int []
, который является типомv
, не имеет конструктора по умолчанию, поэтомуv
он не может быть инициализирован. Согласно определению, значения останутся неопределенными. Запутанная часть заключается в том , что элементыv
уже инициализированы компилятором в1, 2, 3, 4
, поэтому ожидается, что эти значения будут сохранены (поскольку они уже находятся в ячейке памяти), однако со стандартной точки зрения нет определения, будут лиv
эти значения сохраняться послеstd::uninitialized_default_construct(std::begin(v), std::end(v))
.
Комментарии:
1. »
reinterpret_cast
Вызовет первое, которое объявлено какauto
созданное какstruct S*
» к сожалению,S
имеет нетривиальный деструктор, не позволяющий ему быть классом с неявным временем жизни для [class.prop]/9 .2. @L. F. даже если
S
бы был ILT,reinterpret_cast
не создавал бы указатель на объект типаS
из указателя наunsigned char
3. @L. F. согласно [class.dtor]/2 , в случае, если предполагаемый деструктор опущен, то же самое будет неявно объявлено.
4. @jordanvrtanoski Да, но неявно объявленный деструктор нетривиален, потому что деструктор
string
is.5. @L. F. Я вижу смысл, но я не понимаю, как это связано с
std::uninitialized_default_construct()
примером. Приведение в примере работает, потому что оба значения (значение lvalue и значение rvalue) интерпретируются как указатели.