Стандарты программирования на С++. 101 правило и рекомендация | страница 53
>Employee* е = TryToGetEmployee();
>if (е && e->Manager())
> // ...
Корректность этого кода обусловлена тем, что >e->Manager()
не будет вычисляться, если e имеет нулевое значение. Это совершенно обычно и корректно — до тех пор, пока не используется перегруженный оператор >operator&&
, поскольку в таком случае выражение, включающее >&&
, будет следовать правилам функции:
• вызовы функций всегда вычисляют все аргументы до выполнения кода функции;
• порядок вычисления аргументов функций не определен (см. также рекомендацию 31). Давайте рассмотрим модернизированную версию приведенного ранее фрагмента, которая
использует интеллектуальные указатели:
>some_smart_ptr
>if (е && e->Manager())
> // ...
Пусть в этом коде используется перегруженный оператор >operator&&
(предоставленный автором >some_smart_ptr
или >Employee
). Тогда мы получаем код, который для читателя выглядит совершенно корректно, но потенциально может вызвать >e->Manager()
при нулевом значении >e
.
Некоторый иной код может не привести к аварийному завершению программы, но стать некорректным по другой причине — из-за зависимости от порядка вычислений двух выражений. Результат может оказаться плачевным. Например:
>if (DisplayPrompt() && GetLine()) // ...
Если оператор >operator&&
переопределен пользователем, то неизвестно, какая из функций — >DisplayPrompt
или >GetLine
— будет вызвана первой. Программа в результате может ожидать ввода пользователя до того, как будет выведено соответствующее поясняющее приглашение.
Конечно, такой код может заработать при использовании вашего конкретного компилятора и настроек сборки. Но это — очень ненадежно. Компиляторы могут выбрать любой порядок вычислений (и так они и поступают), который сочтут нужным для данного конкретного вызова, принимая во внимание такие факторы, как размер генерируемого кода, доступные регистры, сложность выражений и другие. Так что один и тот же вызов может проявлять себя по-разному в зависимости от версии компилятора, настроек компиляции и даже инструкций, окружающих данный вызов.
Та же ненадежность наблюдается и в случае оператора-запятой. Так же, как и операторы >&&
и >||
, встроенный оператор-запятая гарантирует, что выражения будут вычислены слева направо (в отличие от >&&
и >||
, здесь всегда вычисляются оба выражения). Пользовательский оператор-запятая не может гарантировать вычислений слева направо, что обычно приводит к удивительным результатам. Например, если в следующем коде используется пользовательский оператор-запятая, то неизвестно, получит ли функция