Очки последовательности и метод цепочки - PullRequest
12 голосов
/ 02 апреля 2011

Следующее выражение часто используется для демонстрации undefined неопределенного поведения:

f() + g()

Если f() и g() оба имеют побочные эффекты для некоторого общего объекта, то поведение undefined не указано, поскольку порядок выполнения неизвестен.f() может быть вычислено до g() или наоборот.

Теперь мне было интересно, что происходит, когда вы связываете функции-члены на объекте.Допустим, у меня есть экземпляр класса, экземпляр с именем obj, и он имеет две функции-члена, foo() и bar(), которые оба модифицируют объект.Порядок выполнения этих функций не является коммутативным.Эффект вызова одного перед другим - это не то же самое, что обратный вызов.Оба метода возвращают ссылку на *this, поэтому их можно объединить в цепочку следующим образом:

obj.foo().bar()

Но разве это неуказанное поведение?Я не могу найти ничего в стандарте (по общему признанию, просто просматривая), который отличает это выражение от выражения, которое я дал в верхней части поста.Оба вызова функций являются подвыражениями полного выражения, поэтому их порядок выполнения не определен.Но, конечно, foo() необходимо сначала оценить , чтобы bar() знал, какой объект нужно изменить.

Возможно, я упускаю что-то очевидное, но я не вижу, где точка последовательностисоздан.

Ответы [ 3 ]

10 голосов
/ 02 апреля 2011
f() + g()

Здесь поведение не указано (не undefined ), поскольку порядок, в котором вычисляется каждый операнд (то есть каждая функция вызывается), равен не указано .

 obj.foo().bar();

Это хорошо определено в C ++.

Соответствующий раздел §1.9.17 из стандарта C ++ ISO гласит:

При вызове функции (независимо от того, является ли функция встроенной), существует точка последовательности после оценки всех аргументов функции (если есть) , которая происходит перед выполнением любых выражений или операторовв теле функции .Существует также точка последовательности после копирования возвращаемого значения и перед выполнением любых выражений вне функции .

Подобные случаи обсуждалисьочень подробно, в этих темах:

5 голосов
/ 02 апреля 2011

Если f () и g () оба имеют побочные эффекты для некоторого общего объекта, то поведение не определено, потому что порядок выполнения неизвестен.

Это не правда.Вызовы функций не чередуются, и перед входом в функции и перед выходом из функции существует точка последовательности.Все побочные эффекты в g, относящиеся к побочным эффектам в f, разделены как минимум одной последовательностью.Поведение не является неопределенным.

Как следствие, порядок выполнения функций f и g не определен, но после выполнения одной функции выполняются только оценки этой функции, а другой функции "приходится ждать».Возможны разные наблюдаемые результаты, но это не означает, что произошло неопределенное поведение.

Теперь мне было интересно, что происходит, когда вы цепляете функции-члены на объекте.

Если у вас есть obj.foo().bar(), вам нужно сначала оценить obj.foo(), чтобы узнатьдля какого объекта вы вызываете функцию bar, что означает, что вам нужно ждать, пока obj.foo() вернется и выдаст значение.Это, однако, не обязательно означает, что все побочные эффекты, вызванные оценкой obj.foo(), закончены.После оценки выражения вам потребуется точка последовательности , чтобы эти побочные эффекты считались завершенными.Поскольку существует точка последовательности перед возвратом из obj.foo(), а также перед вызовом bar(), у вас фактически есть определенный порядок выполнения побочных эффектов, инициированный путем вычисления выражений в foo и bar соответственно.

Для более подробного объяснения причина, по которой foo вызывается до bar, в вашем примере аналогична причине, по которой i++ сначала увеличивается до вызова функции f в следующем.

int i = 0;
void f() {
  std::cout << i << std::endl;
}

typedef void (*fptype)();
fptype fs[] = { f };

int main() {
  fs[i++]();
}

Вопрос, который следует задать здесь: будет ли эта программа печатать 0, 1 или ее поведение не определено или не указано?Ответ заключается в том, что выражение fs[i++] обязательно должно быть сначала оценено перед вызовом функции, а перед вводом f существует точка последовательности, значение i внутри f равно 1.

Я не думаю, что вам нужно расширять область действия неявного параметра объекта до точек последовательности, чтобы объяснить ваш случай, и вы, конечно, не можете расширить его, чтобы объяснить этот случай (который, я надеюсь, является определенным поведением).


Черновик C ++ 0x (у которого больше нет точек последовательности) имеет более четкую формулировку этого (выделите мое)

При вызове функции (независимо от того, является ли функция встроенной), каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента, или с выражением постфикса, обозначающим вызываемую функцию , секвенируется перед выполнением каждого выражения или оператора в телевызываемая функция.

1 голос
/ 02 апреля 2011

Foo () будет выполняться перед bar (). Сначала мы должны определить Foo (), иначе мы не будем знать, на что должен действовать bar () (мы даже не будем знать, к какому типу относится класс bar (). Это может быть более интуитивно понятно, если вы думаете, что мы должны были бы сделать, если бы foo возвратил новый экземпляр obj вместо этого, или если foo возвратил экземпляр совершенно другого класса, у которого также определен метод bar ().

Вы можете проверить это самостоятельно, используя точки останова в foo и bar и посмотрев, что попадет первым.

...