Неопределенное поведение и последовательность точек перезагружены
Считайте эту тему продолжением следующей темы:
Предыдущий взнос
Неопределенные точки поведения и последовательности
Давайте вернемся к этому смешному и запутанному выражению (выделенные курсивом фразы взяты из вышеприведенной темы *smile*):
i += ++i;
Мы говорим, что это вызывает неопределенное поведение. Я предполагаю, что когда мы говорим это, мы неявно предполагаем, что i
это один из встроенных типов.
Что делать, если тип i
такое пользовательский тип? Скажите, что его тип Index
который будет определен позже в этом посте (см. ниже). Будет ли оно вызывать неопределенное поведение?
Если да, то почему? Разве это не эквивалентно написанию i.operator+=(i.operator++());
или даже синтаксически проще i.add(i.inc());
? Или они тоже вызывают неопределенное поведение?
Если нет, то почему нет? В конце концов, объект i
изменяется дважды между последовательными точками последовательности. Пожалуйста, помните практическое правило: выражение может изменять значение объекта только один раз между последовательными "точками последовательности. И если i += ++i
является выражением, то оно должно вызывать неопределенное поведение. Если так, то его эквиваленты i.operator+=(i.operator++());
а также i.add(i.inc());
также должен вызывать неопределенное поведение, которое кажется неверным! (насколько я понимаю)
Или же, i += ++i
это не выражение для начала? Если так, то что это такое и каково определение выражения?
Если это выражение и в то же время его поведение также четко определено, то это означает, что число точек последовательности, связанных с выражением, так или иначе зависит от типа операндов, участвующих в выражении. Я прав (даже частично)?
Кстати, как насчет этого выражения?
//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!
a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.
Вы должны учитывать это и в своем ответе (если вы точно знаете его поведение).:-)
Является
++++++i;
четко определены в C++03? В конце концов, это так,
((i.operator++()).operator++()).operator++();
class Index
{
int state;
public:
Index(int s) : state(s) {}
Index& operator++()
{
state++;
return *this;
}
Index& operator+=(const Index & index)
{
state+= index.state;
return *this;
}
operator int()
{
return state;
}
Index & add(const Index & index)
{
state += index.state;
return *this;
}
Index & inc()
{
state++;
return *this;
}
};
5 ответов
Похоже на код
i.operator+=(i.operator ++());
Прекрасно работает в отношении точек последовательности. Раздел 1.9.17 стандарта C++ ISO говорит это о точках последовательности и оценке функций:
При вызове функции (независимо от того, является ли функция встроенной), после вычисления всех аргументов функции (если таковые имеются) существует точка последовательности, которая имеет место перед выполнением любых выражений или операторов в теле функции. Существует также точка последовательности после копирования возвращаемого значения и перед выполнением любых выражений вне функции.
Это будет указывать, например, что i.operator ++()
в качестве параметра для operator +=
имеет точку последовательности после ее оценки. Короче говоря, поскольку перегруженные операторы являются функциями, применяются обычные правила последовательности.
Отличный вопрос, кстати! Мне действительно нравится, как вы заставляете меня понимать все нюансы языка, который, как я уже думал, я знал (и думал, что я думал, что я знал).:-)
http://www.eelis.net/C++/analogliterals.xhtml Аналоговые литералы приходят на ум
unsigned int c = ( o-----o
| !
! !
! !
o-----o ).area;
assert( c == (I-----I) * (I-------I) );
assert( ( o-----o
| !
! !
! !
! !
o-----o ).area == ( o---------o
| !
! !
o---------o ).area );
Как уже говорили другие, ваш i += ++i
Пример работает с пользовательским типом, так как вы вызываете функции, а функции содержат точки последовательности.
С другой стороны, a[++i] = i
не так повезло, если предположить, что a
ваш основной тип массива, или даже определенный пользователем. Проблема в том, что мы не знаем, какая часть выражения, содержащая i
оценивается первым. Это может быть ++i
оценивается, передается operator[]
(или необработанная версия), чтобы получить там объект, а затем значение i
передается к этому (что после того, как я был увеличен). С другой стороны, возможно, последняя сторона сначала оценивается, сохраняется для последующего назначения, а затем ++i
часть оценивается.
Я думаю, что это четко определено:
Из проекта стандарта C++ (n1905) §1.9 / 16:
"Существует также точка последовательности после копирования возвращенного значения и перед выполнением любых выражений вне функции. 13) . Несколько контекстов в C++ вызывают оценку вызова функции, даже если в блоке перевода не появляется соответствующий синтаксис вызова функции. [ Пример: вычисление нового выражения вызывает одну или несколько функций выделения и конструктора; см. 5.3.4. В другом примере вызов функции преобразования (12.3.2) может возникнуть в тех случаях, когда синтаксис вызова функции не появляется. - конец пример ] Точки последовательности на входе в функцию и выходе из функции (как описано выше) являются признаками вычисленных вызовов функции, независимо от того, каким может быть синтаксис выражения, вызывающего функцию ".
Обратите внимание на часть, которую я выделил жирным шрифтом. Это означает, что после вызова функции инкремента действительно есть точка последовательности (i.operator ++()
) но перед вызовом составного присваивания (i.operator+=
).
Хорошо. Пройдя через предыдущие ответы, я переосмыслил свой собственный вопрос, особенно эту часть, на которую пытался ответить только Ной, но я не убежден в нем полностью.
a[++i] = i;
Случай 1:
Если a
это массив встроенного типа. Тогда то, что сказал Ной, верно. То есть,
a [++ i] = i не так повезло, если предположить, что a - это ваш базовый тип массива,
или даже определенный пользователем, Проблема, с которой вы столкнулись, заключается в том, что мы не знаем, какая часть выражения, содержащего i, вычисляется первой.
Так a[++i]=i
вызывает undefined-поведение, или результат не определен. Что бы это ни было, оно не очень четко определено!
PS: в приведенной выше цитате, Зачеркнутые конечно мой.
Случай 2:
Если a
является объектом пользовательского типа, который перегружает operator[]
опять же есть два случая.
- Если возвращаемый тип перегружен
operator[]
функция встроенного типа, затем сноваa[++i]=i
вызывает неопределенное поведение или результат не определен. - Но если возвращаемый тип перегружен
operator[]
Функция определяется пользователем типа, то поведениеa[++i] = i
четко определен (насколько я понимаю), так как в этом случаеa[++i]=i
эквивалентно письмуa.operator[](++i).operator=(i);
что так же, как,a[++i].operator=(i);
, То есть назначениеoperator=
вызывается на возвращенный объектa[++i]
который, кажется, очень четко определен, так как к тому времениa[++i]
возвращается,++i
уже были оценены, а затем возвращенный объект вызываетoperator=
функция, передающая обновленное значениеi
к этому в качестве аргумента. Обратите внимание, что между этими двумя вызовами есть точка последовательности. И синтаксис гарантирует отсутствие конкуренции между этими двумя вызовами, иoperator[]
будет вызван первым, и, следовательно, аргумент++i
передается в него, также будет оцениваться в первую очередь.
Думайте об этом как someInstance.Fun(++k).Gun(10).Sun(k).Tun();
в котором каждый последующий вызов функции возвращает объект определенного пользователем типа. Мне эта ситуация кажется более похожей на это: eat(++k);drink(10);sleep(k)
потому что в обеих ситуациях существует точка последовательности после каждого вызова функции.
Пожалуйста, поправьте меня, если я ошибаюсь.:-)