Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Саттер Герб. Стандарты программирования на С++. 101 правило и рекомендация - royallib.ru.doc
Скачиваний:
59
Добавлен:
11.03.2016
Размер:
715.24 Кб
Скачать

Обсуждение

Главная причина отказа от перегрузки операторов operator&&, operator|| и operator, (запятая) состоит в том, что вы не имеете возможности реализовать полную семантику встроенных операторов в этих трех случаях, а программисты обычно ожидают ее выполнения. В частности, встроенные версии выполняют вычисление слева направо, а для операторов && и || используются сокращенные вычисления.

Встроенные версии && и || сначала вычисляют левую часть выражения, и если она полностью определяет результат (false для &&, true для ||), то вычислять правое выражение незачем — и оно гарантированно не будет вычисляться. Таким образом мы используем эту возможность, позволяя корректности правого выражения зависеть от успешного вычисления левого:

Employee* е = TryToGetEmployee();

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

 // ...

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

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

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

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

some_smart_ptr<Employee> е = TryToGetEmployee();

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

 // ...

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

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

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

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

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

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

int i = 0;

f(i++), g(i); //См. также рекомендацию 31