Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 [ 58 ] 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

менее, вы не можете послать сообщение operator=() константному объекту, потому что его объявление не имеет в конце const:

НЕ ДЕЛАЙТЕ ЭТОГО В ФУНКЦИИ С ИСПОЛЬЗОВАНИЕМ operator=().

const class name &operator=( const class name &r ) const;

Компилятор должен выдать вам ошибку типа "не могу преобразовать ссылку на переменную в ссылку на константу", если вы попробуете (x=y)=z.

Другим спорным моментом в предыдущем коде является сравнение: if( this != &r )

в функции operator=(). Выражение:

class name x; ... x=x;

должно всегда срабатывать, и сравнение this с адресом входного правого аргумента является простым способом в этом убедиться. Имейте в виду, что многие алгоритмы полагают самоприсваивание безвредным, поэтому не делайте его особым случаем. Также имейте в виду, что самоприсваивание могло бы быть затушевано при помощи указателя, как в:

class name array[10]; class name *p = array; ...

*p = array[0];

126. Классы, имеющие члены-указатели, должны

всегда определять конструктор копии и функцию operator=()

Если класс не определяет методы копирования - конструктор копии и функцию operator=() , то это делает компилятор. Созданный компилятором конструктор должен выполнять "почленное" копирование, которое осуществляется таким образом, как будто вы написали this->field = src.field для каждого члена. Это означает, что теоретически должны вызываться конструкторы копий и функции operator=() вложенных объектов и базовых классов. Даже если все работает правильно, все же указатели копируются как указатели. То есть, строка string, представленная как char*, - не строка, а указатель, и



будет скопирован лишь указатель. Представьте, что определение string на листинге 7 со страницы 155 не имеет конструктора копии или функции operator=(). Если вы запишите

string s1 = "фу", s2; ...

s2 = s1;

то это присваивание вместо поля указателя s2 запишет указатель от s1. Та память, которая была адресована посредством s1->buf, теперь потеряна, то есть у вас утечка памяти. Хуже того, если вы меняете s1, то s2 меняется также, потому что они указывают на один и тот же буфер. Наконец, когда строки выходят из области действия, они обе передают buf для освобождения, по сути, очищая его область памяти дважды, и, вероятно, разрушают структуру динамической памяти. Решайте эту проблему путем добавления конструктора копии и функции operator=(), как было сделано на листинге 7 со страницы 155. Теперь копия будет иметь свой собственный буфер с тем же содержанием, что и у буфера строки-источника.

Последнее замечание: я выше написал "должен выполнять" и " теоретически" в первом абзаце, потому что встречал компиляторы, которые фактически выполняли функцию memcpy() в качестве операции копирования по умолчанию, просто как это бы сделал компилятор Си. В этом случае конструктор копии и функция operator=() вложенных объектов не будут вызваны, и вы всегда будете должны обеспечивать конструктор копии и функцию operator=() для копирования вложенных объектов. Если вы желаете достигнуть при этом абсолютной надежности, вы будете должны проделать это для всех классов, чьи члены не являются основными числовыми типами Си++.

127. Если у вас есть доступ к объекту, то он должен быть инициализирован

128. Используйте списки инициализации членов

129. Исходите из того, что члены и базовые классы инициализируются в случайном порядке

Многие неопытные программисты на Си++ избегают списков инициализации членов, как я полагаю, потому, что они выглядят так причудливо. Фактом является то, что большинство программ, которые их не используют, попросту некорректны. Возьмите, например, следующий код (определение строкового класса из листинга 7 со страницы 1 55):



class base

string s; public:

base( const char *init value );

------------------------------

base::base( const char *init value )

s = init value;

Основной принцип такой: если у вас есть доступ к объекту, то он должен быть инициализирован. Так как поле s видимо для конструктора base, то Си++ гарантирует, что оно инициализировано до окончания выполнения тела конструктора. Список инициализации членов является механизмом выбора выполняемого конструктора. Если вы его опускаете, то получите конструктор по умолчанию, у которого нет аргументов, или, как в случае рассматриваемого нами класса string, такой, аргументы которого получают значения по умолчанию. Следовательно, компилятор вначале проинициализирует s пустой строкой, разместив односимвольную строку при помощи new и поместив в нее \0. Затем выполняется тело конструктора и вызывается функция string:: operator=() . Эта функция освобождает только что размещенный буфер, размещает буфер большей длины и инициализирует его значением init value. Ужасно много работы. Лучше сразу проинициализировать объект корректным начальным значением. Используйте:

base( const char *init value ) : s(init value)

Теперь строка s будет инициализирована правильно, и не нужен вызов operator=() для ее повторной инициализации.

Настоящее правило также применимо к базовым классам, доступным из конструктора производного класса, поэтому они должны инициализироваться до выполнения конструктора производного класса. Базовые классы инициализируются перед членами производного класса, потому что члены производного класса невидимы в базовом классе. Подведем итог - объекты инициализируются в следующем порядке:

• Базовые классы в порядке объявления.

• Поля данных в порядке объявления.

Лишь затем выполняется конструктор производного класса. Одно последнее предостережение. Заметьте, что порядок объявления управляет порядком инициализации. Порядок, в котором элементы появляются в



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 [ 58 ] 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76