Программирование >>  Решение нетривиальных задач 

1 ... 65 66 67 [ 68 ] 69 70 71 ... 77


Аналогия проблеме сдвиг как вывод может быть найдена в проектировании компьютерных систем. Большинство проектировщиков аппаратуры были бы счастливы использовать + вместо OR, а * вместо AND, потому что такая запись используется во многих системах проектирования электронных компонентов. Несмотря на это, перегрузка операции operator+() в качестве OR явно не нужна в Си++. К тому же, лексема << означает сдвиг в Си и Си++; она не означает вывод .

Как завершающий пример этой проблемы - я иногда видел реализации класса множество , определяющие и & со значениями объединение и пересечение . Это может иметь смысл для математика, знакомого с таким стилем записи, но при этом не является выражением ни Си, ни Си++, поэтому будет незнакомо для вашего среднего программиста на Си++ (и вследствие этого с трудом сопровождаться). Амперсанд является сокращением для AND; вы не должны назначать ему произвольное значение. Нет абсолютно ничего плохого в a.Union(b) или a.intersect(b) . (Вы не можете использовать a.union(b) со строчной буквой u, потому что union является ключевым словом).

146. Используйте перегрузку операций только для определения операций, имеющих аналог в Си (без сюрпризов)

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

Тем не менее, также разумно использовать перегруженные операции и там, где аналогии с Си незаметны. Например, большинство классов будет перегружать присваивание. Перегрузка operator==() и operator !=() также разумна в большинстве классов.

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

string array[ size ]; string *p = array;

for( int i = size; -- i >= 0 ; )

visit( *p++ ); функции visit() передается строка.



Операция

Описание

ti = t;

Возврат к началу последовательности

--ti;

Возврат к предыдущему элементу

ti += i;

Переместить вперед на i элементов

ti -= i;

Переместить назад на i элементов

ti + i; ti - i;

Присваивает итератору другой временной переменной значение с указанным смещением от ti

ti[i];

Элемент со смещением i от текущей позиции

ti[-i];

Элемент со смещением -i от текущей позиции

t2 = ti;

Скопировать позицию из одного итератора в другой

t2 - ti;

Расстояние между двумя элементами, адресуемыми различными итераторами

Аналог в Си++ может выглядеть вот так (keys является деревом, чьи узлы имеют строковые ключи; здесь могут быть любые другие структуры данных):

tree<string> keys; двоичное дерево с узлами, имеющими

строковые ключи

iterator p = keys;

...

for( int i = keys.size(); --i >= 0 ; )

visit( *p++ ); функции visit() передается строка.

Другими словами, вы обращаетесь с деревом как с массивом, и можете итерировать его при помощи итератора, действующего как указатель на элемент. И так как iterator(p) ведет себя точно как указатель в Си, то правило без сюрпризов не нарушается.

147. Перегрузив одну операцию, вы должны перегрузить все сходные с ней операции

Это правило является продолжением предыдущего. После того, как вы сказали, что итератор работает во всем подобно указателю , он на самом деле должен так работать. Пример в предыдущем правиле использовал лишь перегруженные * и ++, но моя настоящая реализация итератора делает аналогию полной, поддерживая все операции с указателями. Таблица 4 показывает различные возможности (t является деревом, а ti - итератором для дерева). Обе операции *++p и *p++ должны работать и т.д. В предыдущем примере я бы должен был также перегрузить в классе tree операции operator [] и (унарная) operator*() для того, чтобы аналогия дерева с массивом выдерживалась везде. Вы уловили эту мысль?

Таблица 4. Перегрузка операторов в итераторе



ti->msg();

Послать сообщение этому элементу

(*ti).msg();

Послать сообщение этому элементу

Одна из проблем здесь связана с операциями operator==() и operator ! = (), которые при первом взгляде кажутся имеющими смысл в ситуациях, где другие операции сравнения бессмысленны. Например, вы можете использовать == для проверки двух окружностей на равенство, но означает ли равенство одинаковые координаты и одинаковый радиус , или просто одинаковый радиус ? Перегрузка других операций сравнения типа < или <= еще более сомнительна, потому что их значение не совсем очевидно. Лучше полностью избегать перегрузки операций, если есть какая-либо неясность в их значении.

148. Перегруженные операции должны работать точно так же, как они работают в Си

Главной новой проблемой здесь являются адресные типы lvalue и rvalue. Выражения типа lvalue легко описываются в терминах Си++: они являются просто ссылками. Компилятор Си, вычисляя выражение, выполняет операции по одной за раз в порядке, определяемом правилами сочетательности и старшинства операций. Каждый этап в вычислениях использует временную переменную, полученную при предыдущей операции. Некоторые операции генерируют rvalue - действительные объекты, на самом деле содержащие значение. Другие операции создают lvalue - ссылки на объекты. (Кстати, l и r используются потому, что в выражении l=r слева от = генерируется тип lvalue. Справа образуется тип rvalue).

Вы можете сократить эффект неожиданности для своего читателя, заставив свои перегруженные операции-функции работать тождественно их эквивалентам на Си в пределах того, что они могут. Далее описано, как работают операции Си и как имитировать их поведение:

Операции присваивания (=, +=, -= и т.д.) и операции автоинкремента и автодекремента (++, -) требуют операндов типа lvalue для адресата - части, которая изменяется. Представьте ++ как эквивалент для +=1, чтобы понять, почему эта операция в той же категории, что и присваивание.

В перегруженных операциях функций-членов указатель this на самом деле является lvalue, поэтому здесь не о чем беспокоиться. На глобальном уровне левый операнд перегруженной бинарной операции присваивания (и единственный операнд перегруженной унарной операции присваивания) должен быть ссылкой.



1 ... 65 66 67 [ 68 ] 69 70 71 ... 77

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