Стандарты программирования на С++. 101 правило и рекомендация | страница 53



>Employee* е = TryToGetEmployee();

>if (е && e->Manager())

> // ...

Корректность этого кода обусловлена тем, что >e->Manager() не будет вычисляться, если e имеет нулевое значение. Это совершенно обычно и корректно — до тех пор, пока не используется перегруженный оператор >operator&&, поскольку в таком случае выражение, включающее >&&, будет следовать правилам функции:

• вызовы функций всегда вычисляют все аргументы до выполнения кода функции;

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

использует интеллектуальные указатели:

>some_smart_ptr е = TryToGetEmployee();

>if (е && e->Manager())

> // ...

Пусть в этом коде используется перегруженный оператор >operator&& (предоставленный автором >some_smart_ptr или >Employee). Тогда мы получаем код, который для читателя выглядит совершенно корректно, но потенциально может вызвать >e->Manager() при нулевом значении >e.

Некоторый иной код может не привести к аварийному завершению программы, но стать некорректным по другой причине — из-за зависимости от порядка вычислений двух выражений. Результат может оказаться плачевным. Например:

>if (DisplayPrompt() && GetLine()) // ...

Если оператор >operator&& переопределен пользователем, то неизвестно, какая из функций — >DisplayPrompt или >GetLine — будет вызвана первой. Программа в результате может ожидать ввода пользователя до того, как будет выведено соответствующее поясняющее приглашение.

Конечно, такой код может заработать при использовании вашего конкретного компилятора и настроек сборки. Но это — очень ненадежно. Компиляторы могут выбрать любой порядок вычислений (и так они и поступают), который сочтут нужным для данного конкретного вызова, принимая во внимание такие факторы, как размер генерируемого кода, доступные регистры, сложность выражений и другие. Так что один и тот же вызов может проявлять себя по-разному в зависимости от версии компилятора, настроек компиляции и даже инструкций, окружающих данный вызов.

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