Шаг 13 - Перегрузка operator+

Оператор operator-> мы уже перегружали. Результаты получились просто феерические. Давайте замучаем еще кого-нибудь и посмотрим, что получится? Давайте. Первейшим кандидатом на переопределение является оператор operator+,потому что в жизни (помимо С++) он выражает замечательное действие - добавление объекта к другому объекту. Конечно, Вы можете придать ему и другой смысл, но это будет как раз тот случай, где "если хочешь быть здоров - ешь один, и в темноте"...

Проиллюстрирую сказанное следующим примером: Вы пишете интерфейс на Паскале, и для добавления подменю или пункта меню используете функции AddSubMenu() и AddMenuItem(). Тогда для создания меню Вы прописываете замечательно изящное выражение с толстым-толстым слоем скобок в конце:

Menu.AddSubMenu( Submenu1,
	AddMenuItem( MenuItem1,
	AddMenuItem( MenuItem2,
	AddMenuItem( MenuItem3,
	AddSubMenu( SubMenu2, nil)))));

А вот что получается на С++:

CMenu Menu = Menu() + SubMenu1() + MenuItem1() +
	 MenuItem2() + MenuItem3() + SubMenu2();

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

Есть еще один смысл, который можно придать оператору operator+, если "от перестановки мест слагаемых сумма не изменяется". Примером, кроме сложения и умножения чисел, может быть соударение объектов: столкнулся ли Титаник с айсбергом, айсберг ли столкнулся с Титаником, результат один - вызов деструктора для Титаника. (Я не утверждаю, что это НУЖНО делать так, это только можно!)

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

class CArray
{
private:
	int a[100];
	int iTop;
public:
	CArray ():iTop(0){};
	CArray (const CArray& _ca)
	{
		iTop = _ca.iTop;
		for (int i=0; i++; i <100) a[i]= _ca.a[i];
	} 
	CArray& operator=(const CArray& _ca)
	{
		if (this==&_ca) return *this;
		for (int i=0; i++; i <100) a[i]= _ca.a[i];
		iTop = _ca.iTop;
		return *this;
	};
// С этим еще можно согласиться
	CArray& operator+ (int _i) { a[iTop]=_i; iTop++; return *this; };
// Это пример дурного стиля
	int operator-- () { iTop--; return a[iTop+1]; };
};

Для такого тестового стека определим оператор operator+operator+=) для вставки объектов, а оператор operator-- для выталкивания и удаления. Вы кстати знаете, что для operator++ и operator-- есть постфиксная и префиксная форма? У постфиксной в скобках фиктивный параметр стоит:

const type& operator++(); // Префиксная
type operator++(int);  // Постфиксная.

Я позволяю себе пропустить кучу деталей (например то, что вставка сто первого элемента слегка повесит программу), но обращаю внимание: определение конструктора копии и оператора присваивания - суровая необходимость. Не сделаете - пожалеете; почему - говорил в Шаге 5.

Еще нюанс: определение operator-- для выбора из стека есть, мягко говоря, спорный стиль; некоторые считают даже перегрузку operator<< и operator>> для работы с потоками крайне неудачной идеей. Так что это я только для примера...

Пример имеет семантику значений, а не семантику указателей. Это в общем значит, что в коллекции хранятся примитивные значения, а не указатели или ссылки.

Кстати, запомните эти умные слова, их круто вставить в разговор с приятелями или (еще лучше) с начальством, чтобы запутать его в камом-нибудь вопросе или снять с себя ответственность за неверно принятые решения. Если особенно ответственность лежит так же и на начальстве (оно же и аналитик), то объяснение "так и так, тут семантика значений" будет воспринято лучше, чем "вы все - тупицы и недоучки, неспособные к проектированию и анализу". Вообще, теоретические познания бывают как нельзя более кстати при поиске виновных, чем при повседневной работе. Вы можете колбасить программы на фокспре или хотя и на C++ c MTS и DCOM, но если Ваша программа повесит сервер бухгалтерии за день до зарплаты или годового отчета... то никакие познания не будут лишними, чтобы свалить вину на сисадминов!

Перед тем, как закончить Шаг, вернусь к стилю: если я для примера безграмотно переопределил операторы арифметики, это не значит, что то же самое должны делать Вы. Компилятор позволяет определить любое возвращаемое значение и запрограммировать любые действия, но понемногу изменяя семантику, в конце концов Вы выроете себе яму и попадете в нее. Есть еще одна опасность - поведение, приоритет и правила взаимодействия операторов ЗАДАНЫ раз и навсегда, а компилятор не может оценить Ваш интеллектуальный уровень, и действует так, как ДОЛЖЕН, а не так, как Вы ДУМАЕТЕ, что он должен. Из-за этого НИКОГДА не переопределяйте операторы operator&&() и operator||(), и всегда правильно задавайте возвращаемое значение операторов.


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Albert Makhmutov - 13.04.2001