45
public:
Foo& operator=(const Foo&);
};
class Bar {
public:
//
Оператор = не перегружен
};
class FooBar {
private:
Foo
f;
Bar
b;
public:
FooBar& operator=(const FooBar&);
};
FooBar& FooBar::operator=(const FooBar& fb)
{
if (this == &fb) return *this;
f = fb.f; //
Используется перегруженный оператор = класса Foo
f = fb.b; //
Используется оператор = по умолчанию
return
*this;
}
Применяя эту методику, вы не заботитесь о том, существует ли для переменной перегруженный
оператор
=
. Об этом должен думать компилятор.
Присваивание для базовых классов
Присваивание для базовых классов сопряжено с некоторыми синтаксическими ухищрениями. Если вы
никогда их не видели, вероятно, на поиск правильной комбинации уйдет немало времени. Выглядит
она так:
class Foo {...}
class Bar : public Foo {
public:
Bar& operator=(const Bar&);
};
Bar& Bar::operator=(const Bar& b)
{
if (this == &b) return *this;
this->Foo::operator=(b);
//
Чего-чего?
return
*this;
}
Другие варианты, которые могут придти в голову (например,
*((Foo)this)=b;
), не работают —
поверьте мне на слово. Все они создают временные копии. Показанный вариант работает, поскольку
компилятор знает, как преобразовать
Bar
в
Foo
в аргументе. Он работает независимо от того,
перегружали вы
Foo::operator=
или нет. Даже если не перегружали, оператор все равно
присутствует, и его можно вызвать по полному имени
Foo::operator=
.
Другие сигнатуры оператора =
Оператор
=
не ограничен одной сигнатурой. Его можно перегрузить так, чтобы в правой части
присваивания мог стоять аргумент любого другого типа. Сигнатура
Х& X::operator=(const Х&)
выделяется на общем фоне тем, что компилятор предоставляет ее версию по умолчанию и использует
эту сигнатуру в стандартном алгоритме рекурсивного присваивания.
46
class String {
// Как раньше
public:
String& operator=(const String&);
// Нормальный вариант
String&
operator=(char*);
//
Перегруженный вариант
String&
operator=(int);
//
Вызывает atoi()
};
В показанном фрагменте создается несколько перегруженных вариантов оператора
=
для различных
типов данных в правой части выражения. Второй вариант позволяет избежать конструирования
временного объекта
String
из
char*
лишь для того, чтобы присвоить его объекту в левой части.
Третий вариант выполняет преобразование другого рода. Тем не менее, лишь первый вариант
перегружает (то есть заменяет) версию оператора по умолчанию.
Перегрузка операторов
Одна из приятных особенностей C++ — возможность расширения смысла операторов. Это упрощает
чтение программы, поскольку вам уже не придется изобретать дурацкие имена функций вроде
Add
там,
где знак
+
имеет совершенно очевидный смысл. Тем не менее, из личного опыта я знаю две проблемы,
связанные с перегруженными операторами. Во-первых, их чрезмерное применение превращает
программу в хаос. Во-вторых, большинство программистов никогда их не использует. Приведенный
ниже список не претендует на полноту, однако он поможет подготовить поле для дальнейшего
изложения материала.
Функциональная форма операторов
Операторы (например,
+
) используются в двух вариантах: как особая синтаксическая форма или как
функция. В C++ функциональная форма всегда представляет собой ключевое слово
operator
, за
которым следует символ оператора.
class Foo {...}
Foo x, y, z;
z = x + y;
// Инфиксная (нормальная) форма
z = operator+(x, y);
// Функциональная форма (внешняя функция)
z = x.operator+(y);
// Функциональная форма (функция класса)
С концептуальной точки зрения три последние строки эквивалентны, хотя на практике, вероятно,
оператор будет определен либо в виде внешней функции, либо в виде функции класса, но не в обоих
вариантах сразу. Для бинарных операторов знак оператора указывается между двух аргументов в
инфиксной форме. В форме внешней функции оба аргумента передаются глобальной функции. В
форме функции класса объект, которому принадлежит вызываемый оператор, указывается слева, а
аргумент — справа от знака оператора. Унарные операторы (такие как
!
и
~
) тоже могут
перегружаться. Форма внешней функции вызывается с одним аргументом, а форма функции класса
вызывается без аргументов (операция выполняется с объектом, находящимся слева от оператора
.
или
->
).
Не разрешается перегружать встроенные операторы (например, оператор целочисленного сложения).
Чтобы обеспечить выполнение этого условия, компилятор требует, чтобы хотя бы один аргумент
каждого перегруженного оператора относился к пользовательскому типу (обычно к классу). Выбор
ограничен операторами, уже определенными в C++. Во время долгих ночных отладок мне часто хоте-
лось создать оператор с именем
#$%^&
, но C++ на этот счет неумолим.
Перегруженные операторы наследуют приоритеты и атрибуты группировки от встроенных операторов,
поэтому вы не можете, например, изменить стандартный порядок группировки «слева направо» для
оператора
+
. Не существует ограничений на тип значения, возвращаемого перегруженным оператором,
и один оператор можно перегружать произвольное число раз при условии, что сигнатуры остаются
уникальными.
Do'stlaringiz bilan baham: |