Имеет ли следующая программа неопределенное поведение в C ++ 17 и более поздних версиях?
struct A {
void f(int) { /* Assume there is no access to *this here */ }
};
int main() {
auto a = new A;
a->f((a->~A(), 0));
}
C ++ 17 гарантирует, что a->f
вычисляется для функции-члена объекта A
доаргумент вызова оценивается.Следовательно, направление от ->
четко определено.Но перед вводом вызова функции, аргумент оценивается и заканчивает время жизни объекта A
(см. Однако правки ниже).У вызова все еще есть неопределенное поведение?Можно ли таким способом вызывать функцию-член объекта вне его времени жизни?
Категория значения a->f
имеет значение prvalue на [expr.ref] /6.3.2 и [basic.life] / 7 запрещает только вызовы нестатических функций-членов для glvalues , ссылающихся на объект после жизни.Означает ли это, что вызов действителен?(Изменить: как обсуждалось в комментариях, я, вероятно, неправильно понимаю [basic.life] / 7, и это, вероятно, применимо здесь.)
Изменится ли ответ, если я заменю вызов деструктора a->~A()
на delete a
или new(a) A
(с #include<new>
)?
Некоторые уточнения и уточнения по моему вопросу:
Если бы я разделял вызов функции-члена и деструктор/ delete /place-new в двух утверждениях, я думаю, что ответы ясны:
a->A(); a->f(0)
: UB из-за нестатического вызова члена на a
вне его времени жизни.(см. редактирование ниже) delete a; a->f(0)
: то же, что и выше new(a) A; a->f(0)
: четко определено, вызов нового объекта
Однако ввсе эти случаи a->f
упорядочены после первого соответствующего оператора, в то время как в моем первоначальном примере этот порядок обратный.Мой вопрос состоит в том, допускает ли это изменение ответы на изменения?
Для стандартов до C ++ 17 я изначально полагал, что все три случая вызывают неопределенное поведение, потому что оценка a->f
зависитна значение a
, но не является последовательным по отношению к оценке аргумента, который вызывает побочный эффект на a
.Однако это неопределенное поведение, только если есть фактический побочный эффект от скалярного значения, например, запись в скалярный объект.Однако ни один скалярный объект не записан, потому что A
является тривиальным, и поэтому мне также было бы интересно узнать, какое именно ограничение нарушается в случае стандартов до C ++ 17.В частности, мне сейчас неясен случай размещения новых.
Я только что понял, что формулировка относительно времени жизни объектов изменилась между C ++ 17 и текущим проектом.В n4659 (черновик C ++ 17) [basic.life] / 1 гласит:
Время жизни объекта o типа T заканчивается, когда:
- , если Tэто тип класса с нетривиальным деструктором (15.4), вызов деструктора начинается с
[...]
, тогда как текущий черновик говорит:
Время жизни объекта o типа T заканчивается, когда:
[...]
- , если T является типом классаначинается вызов деструктора, или
[...]
Поэтому, я полагаю, мой пример действительно имеет четко определенное поведение в C ++ 17, ноэто не текущий (C ++ 20) черновик, потому что вызов деструктора тривиален и время жизни объекта A
фактически не заканчивается.Я был бы признателен за разъяснение этого также.Мой оригинальный вопрос все еще стоит даже для C ++ 17 в случае замены вызова деструктора выражением delete или place-new.
Если f
обращается к *this
в своем теле,тогда, очевидно, может быть неопределенное поведение для случаев вызова деструктора и выражения удаления, однако в этом вопросе я хочу сосредоточиться на том, действителен ли сам вызов или нет.Однако обратите внимание, что вариант моего вопроса с Placement-New потенциально не будет иметь проблемы с доступом к элементу в f
, в зависимости от того, является ли сам вызов неопределенным поведением или нет.Но в этом случае может возникнуть дополнительный вопрос, особенно для случая размещения нового, потому что мне неясно, будет ли this
в функции всегда автоматически ссылаться на новый объект или может потребоваться потенциальнобыть std::launder
ed (в зависимости от того, какие элементы A
имеет).
Хотя A
имеет тривиальный деструктор, более интересным является случай, когда он имеет некоторый побочный эффект, относительно которогоКомпилятор может захотеть сделать предположения для целей оптимизации.(Я не знаю, использует ли какой-либо компилятор что-то подобное.) Поэтому я приветствую ответы для случая, когда A
также имеет нетривиальный деструктор, особенно если ответ отличается в двух случаях.
Кроме того, с практической точки зрения, тривиальный вызов деструктора, вероятно, не влияет на сгенерированный код и (маловероятно?) Оптимизацию, основанную на неопределенных предположениях о поведении, за исключением того, что все примеры кода, скорее всего, сгенерируют код, который работает как ожидается на большинстве компиляторов.,Меня больше интересует теоретическая, а не практическая перспектива.
Цель этого вопроса - лучше понять детали языка.Я не призываю никого писать такой код.