++ Kristo!
Стандарт C ++ 1.9.16 имеет большой смысл в том, как реализовать оператор ++ (postfix) для класса. Когда вызывается метод operator ++ (int), он увеличивает себя и возвращает копию исходного значения. Точно так же, как в спецификации C ++.
Приятно видеть, как улучшаются стандарты!
Однако я отчетливо помню использование более старых (до ANSI) компиляторов C, в которых:
foo -> bar(i++) -> charlie(i++);
Не сделал то, что вы думаете! Вместо этого он скомпилировал эквивалент:
foo -> bar(i) -> charlie(i); ++i; ++i;
И это поведение зависело от реализации компилятора. (Портируя веселье.)
Достаточно просто проверить и убедиться, что современные компиляторы теперь ведут себя правильно:
#define SHOW(S,X) cout << S << ": " # X " = " << (X) << endl
struct Foo
{
Foo & bar(const char * theString, int theI)
{ SHOW(theString, theI); return *this; }
};
int
main()
{
Foo f;
int i = 0;
f . bar("A",i) . bar("B",i++) . bar("C",i) . bar("D",i);
SHOW("END ",i);
}
Ответ на комментарий в теме ...
... И опираясь в значительной степени на У КАЖДОГО ответов ... (Спасибо, ребята!)
Я думаю, нам нужно объяснить это немного лучше:
Дано:
baz(g(),h());
Тогда мы не знаем, будет ли g () вызываться до или после h () . Это "не указано" .
Но мы знаем, что и g () и h () будут вызваны до baz () .
Дано:
bar(i++,i++);
Опять же, мы не знаем, какой i ++ будет оцениваться первым, и, возможно, даже не будет, будет ли i увеличен один или два раза до bar () называется. Результаты не определены! (Дано i = 0 , это может быть бар (0,0) или бар (1,0) или бар (0,1) или что-то действительно странное!)
Дано:
foo(i++);
Теперь мы знаем, что i будет увеличен до вызова foo () . Как Кристо указывает из стандартного раздела C ++ 1.9.16:
При вызове функции (независимо от того, является ли функция встроенной), каждое вычисление значения и побочный эффект, связанные с любым выражением аргумента или с выражением постфикса, обозначающим вызываемую функцию, упорядочиваются перед выполнением каждого выражения или оператора в тело вызываемой функции. [Примечание: вычисления значений и побочные эффекты, связанные с различными выражениями аргументов, не являются последовательными. - примечание конца]
Хотя я думаю, что в разделе 5.2.6 это сказано лучше:
Значением выражения postfix ++ является значение его операнда. [Примечание: полученное значение является копией исходного значения - примечание конца] Операндом должно быть изменяемое значение l. Тип операнда должен быть арифметическим типом или указателем на полный эффективный тип объекта. Значение объекта операнда изменяется, добавляя 1 к нему, если объект не имеет тип bool, в этом случае это установлено в true. [Примечание: это использование устарело, см. Приложение D. - примечание к концу]. Вычисление значения выражения ++ выполняется до модификации объекта операнда. Что касается вызова функции с неопределенной последовательностью, операция postfix ++ представляет собой единственную оценку. [Примечание: Следовательно, вызов функции не должен вмешиваться между преобразованием lvalue-to-rvalue и побочным эффектом, связанным с любым отдельным оператором postfix ++. - примечание конца] Результат - значение. Тип результата - cv-неквалифицированная версия типа операнда. См. Также 5.7 и 5.17.
Стандарт, в разделе 1.9.16, также перечисляет (как часть его примеров):
i = 7, i++, i++; // i becomes 9 (valid)
f(i = -1, i = -1); // the behavior is undefined
И мы можем тривиально продемонстрировать это с помощью:
#define SHOW(X) cout << # X " = " << (X) << endl
int i = 0; /* Yes, it's global! */
void foo(int theI) { SHOW(theI); SHOW(i); }
int main() { foo(i++); }
Итак, да, i увеличивается до вызова foo () .
Все это имеет большой смысл с точки зрения:
class Foo
{
public:
Foo operator++(int) {...} /* Postfix variant */
}
int main() { Foo f; delta( f++ ); }
Здесь Foo :: operator ++ (int) должен быть вызван до delta () . И операция приращения должна быть завершена во время этого вызова.
В моем (возможно, слишком сложном) примере:
f . bar("A",i) . bar("B",i++) . bar("C",i) . bar("D",i);
f.bar ("A", i) необходимо выполнить для получения объекта, используемого для object.bar ("B", i ++) и т. Д. Для "C" и "D" .
Итак, мы знаем, что i ++ увеличивает i до вызова bar ("B", i ++) (даже если bar ("B") , ...) вызывается со старым значением i ), и поэтому i увеличивается до bar ("C", i) и бар ("D", i) .
Возвращаясь к j_random_hacker комментарий:
j_random_hacker пишет:
+1, но мне пришлось внимательно прочитать стандарт, чтобы убедить себя, что это нормально. Правильно ли я считаю, что если вместо bar () была глобальная функция, возвращающая, скажем, int, f была int, и эти вызовы были связаны, скажем, «^» вместо «.», То любой из A, C и D мог сообщить "0"?
Этот вопрос намного сложнее, чем вы думаете ...
Переписать ваш вопрос как код ...
int bar(const char * theString, int theI) { SHOW(...); return i; }
bar("A",i) ^ bar("B",i++) ^ bar("C",i) ^ bar("D",i);
Теперь у нас есть только ONE выражение. Согласно стандарту (раздел 1.9, стр. 8, pdf, стр. 20):
Примечание: операторы могут быть перегруппированы в соответствии с обычными математическими правилами только тогда, когда операторы действительно ассоциативны или коммутативны. (7) Например, в следующем фрагменте: a = a + 32760 + b + 5; оператор выражения ведет себя точно так же, как: a = (((a + 32760) + b) +5); из-за ассоциативности и приоритета этих операторов. Таким образом, результат суммы (a + 32760) затем добавляется к b, а затем этот результат добавляется к 5, что приводит к значению, назначенному для a. На машине, в которой переполнения создают исключение и в которой диапазон значений, представляемых целым числом, равен [-32768, + 32767], реализация не может переписать это выражение как a = ((a + b) +32765); поскольку, если бы значения a и b были, соответственно, -32754 и -15, сумма a + b вызвала бы исключение, а исходное выражение - нет; и при этом выражение не может быть переписано как a = ((a + 32765) + b); или а = (а + (б + 32765)); поскольку значения для a и b могли быть соответственно 4 и -8 или -17 и 12. Однако на машине, в которой переполнения не приводят к исключению, и в которых результаты переполнений являются обратимыми, выше выражение-выражение может быть переписано реализацией любым из вышеперечисленных способов, поскольку будет получен тот же результат. - конец примечания]
Таким образом, мы можем думать, что из-за старшинства наше выражение будет таким же, как:
(
(
( bar("A",i) ^ bar("B",i++)
)
^ bar("C",i)
)
^ bar("D",i)
);
Но, поскольку (a ^ b) ^ c == a ^ (b ^ c) без каких-либо возможных ситуаций переполнения, его можно переписать в любом порядке ...
Но поскольку вызывается bar () и может гипотетически вызывать побочные эффекты, это выражение нельзя переписать в любом порядке. Правила приоритета все еще применяются.
Что приятно определяет порядок вычисления bar () .
Теперь, когда это i + = 1 происходит? Ну, это все еще должно произойти, прежде чем bar ("B", ...) вызывается. (Даже если bar ("B", ....) вызывается со старым значением.)
Таким образом, это определенно происходит до бара (C) и бара (D) , и после бара (A) .
Ответ: НЕТ . У нас всегда будет «A = 0, B = 0, C = 1, D = 1», , если компилятор соответствует стандартам.
Но рассмотрим еще одну проблему:
i = 0;
int & j = i;
R = i ^ i++ ^ j;
Какое значение R?
Если бы i + = 1 произошло до j , мы бы получили 0 ^ 0 ^ 1 = 1. Но если бы i + = 1 произошло после всего выражения, мы бы получили 0 ^ 0 ^ 0 = 0.
На самом деле, R равно нулю. i + = 1 не происходит до тех пор, пока выражение не будет оценено.
Вот почему я считаю:
i = 7, i ++, i ++; // мне становится 9 (действительный)
Законно ... имеет три выражения:
И в каждом случае значение i изменяется в конце каждого выражения. (До того, как будут оценены любые последующие выражения.)
PS: Рассмотрим:
int foo(int theI) { SHOW(theI); SHOW(i); return theI; }
i = 0;
int & j = i;
R = i ^ i++ ^ foo(j);
В этом случае i + = 1 должно быть оценено перед foo (j) . theI равно 1. И R равно 0 ^ 0 ^ 1 = 1.