Программирование >>  Оптимизация возвращаемого значения 

1 ... 9 10 11 [ 12 ] 13 14 15 ... 96


К сожалению, не все компиляторы поддерживают это ограничение. Прежде чем полагаться на компиляторы, протестируйте их.

При этом ясно, что второй вызов operator++ применяется к объекту, возвращаемому в результате первого вызова.

Есть две причины, по которым нужно отказаться от такой возможности. Во-первых, она несовместима с поведением встроенных типов. При конструировании классов существует хорошее правило: если сомневаешься, обработай аналогично типу int, а тип int уж точно не позволяет двойное применение постфиксного инкрементного оператора:

int i ;

i++++; Ошибка!

Вторая причина состоит в том, что двойное применение постфиксного инкрементного оператора почти никогда не соответствует потребностям клиента. Как уже было отмечено выше, второй вызов operator++ изменяет значение объекта, возвращаемого после первого вызова, а не значение исходного объекта. Таким образом, если бы выражение

было корректным, то значение переменной i увеличивалось бы только один раз. Это противоречит привычной практике и может приводить к ошибкам (и для типа int, и для типа UPInt), так что такой синтаксис Л5ше запретить.

Язык C-I-I- запрещает его для типа int, но для классов, написанных пользователем, программа должна делать это самостоятельно. Самый простой путь состоит в том, чтобы придать значению, возвращаемому постфиксным инкрементным оператором, атрибут const. Тогда если компиляторы видят выражение

!++++; Эквивалентноi.operator++(0).operator++(0) .

они понимают, что объект const, возвращаемый после первого вызова, используется для повторного вызова operator++. Однако operator++ имеет атрибут const, поэтому объекты с данным атрибутом не могут вызывать этот оператор*. Итак, теперь вы знаете, что функции, возвращающие значение с атрибутом const, иногда нужны, например для использования в инкрементном и декрементном операторах.

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



префиксную форму при каждой возможности, потому что она обеспечивает более эффективный код.

И еще одно замечание относительно префиксной и постфиксной форм. Если не обращать внимания на возвращаемые значения, обе они делают одно и то же: увеличивают значение переменной. То есть предполагается, что они делают одно и то же. Как же обеспечить согласованность их поведения? Какую гарантию можно дать, что со временем, например в результате действий различных программистов, поддерживающих программное обеспечение, реализации операторов не начнут отличаться? Это можно гарантировать, только следуя вышеописанным принципам. В соответствии с ними постфиксная форма декрементного и инкрементного операторов должна быть реализована через их префиксную форму. Тогда придется поддерживать только префиксную форму, потому что постфиксная будет вести себя аналогично.

Как вы видите, легко овладеть постфиксной и префиксной формами декрементного и инкрементного операторов. Если правильно задать тип возвращаемого значения и реализовать постфиксную форму оператора через префиксную, то дальше работы остается немного.

Правило 7. Никогда не перегружайте операторы &&, и,

Как и язык С, С++ использует оптимизированную схему оценки логических выражений. Это означает, что вычисление выражения заканчивается, как только установлена его истинность или ложность, даже если рассмотрены не все части выражения. Например, в следующем случае:

char *р;

if ( (р ! = 0) && {strlen(p) >10) ) . . .

не нужно волноваться по поводу вызова функции strlen с нулевым указателем, потому что при р = О функция strlen попросту не будет вызвана. Аналогично, в функции

int rangeCheck{int index) {

if ( (index< lowerBound) I I {index>upperBound) ) . . .

сравнение значений index и upperBound никогда не произойдет, если значение переменной index меньше lowerBound. ,

Этот образ мышления свойственен программистам на С и С++ с незапамятных времен, и они ожидают такого поведения. Более того, все написанные ими программы рассчитаны на оптимизацию оценки логических выражений. Например, в первом фрагменте кода, приведенном выше, существенно, чтобы функция strlen не могла быть вызвана с нулевым аргументом, потому что, как утверждает



стандарт языка С++ (и стандарт С тоже), результат вызова функции strlen с нулевым указателем не определен.

Язык С++ позволяет программисту модифицировать поведение операторов && и I I для определенных пользователем типов. Это можно сделать, перегрузив функции operator&S; и operator I I как глобально, так и внутри некоторых классов. Однако если решите пойти по этому пути, вы должны отдавать себе отчет, что радикально изменяете правила игры, поскольку оптимизирующая семантика меняется на семантику вызовов функций. Это означает, что перегруженный operator&&, который для вас выглядит как

if (expressionl && expression2) ...

ДЛЯ компилятора выглядит следующим образом:

if (expressionl.operator&&(expression2)) ...

operator&& является функцией-членом. if (operator&b(expressionl,expression2)) ...

operator&& является глобальной функцией.

Семантика вызова функции имеет два чрезвычайно важных отличия от оптимизированной семантики оценки логического выражения. Во-первых, при вызове функции вычисляются все ее аргументы, поэтому при вызове функций operator&& и operator I I вычисляются выражения справа и слева от символа оператора. Иными словами, этот вариант делает код менее оптимальным. Во-вторых, спецификация языка не определяет порядок вычисления аргументов функции, поэтому невозможно определить, какое из двух выражений, expressionl или expression2, будет вычислено первым. Это также полностью противоречит оптимизированной оценке логических выражений, при которой аргументы всегда вычисляются слева направо.

В результате при перегрузке операторов && или I I не существует способа создать операторы, которые ведут себя ожидаемым и привычным образом. Поэтому операторы && и I вообще не следует перегружать.

Ситуация с оператором-запятая (,) выглядит аналогично, но прежде чем рассматривать его свойства, стоит пояснить, для чего он нужен.

Оператор-запятая используется для формирования выражений, и вполне вероятно, что вы встречали его в цикле for. Приведенная ниже функция, например, почти аналогична функции из второго издания классической работы Кернигана и Ричи (Kernighan and Ritchie) Программирование на языке С (The С Programming Language, Prentice-Hall, 1988):

Меняет порядок следования симоволов / / в строке S на обратный. voidreverse(char s[]) {

for (int i=0, j=strlen(s) -1; i , J;

++i, --j) Авотиоператор-запятая!



1 ... 9 10 11 [ 12 ] 13 14 15 ... 96

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика