49
{
//
Вероятно, здесь следует проверить, что строка
//
представляет собой число, принадлежащее к диапазону длинных целых
return
atoll(s);
}
String s(“1234”);
long x = s;
// Вызывается функция operator long()
Операторы преобразования должны быть функциями класса. Как видно из показанного фрагмента,
операторы преобразования хороши тем, что компилятор обычно сам может определить, когда они
должны вызываться. Если ему понадобится длинное целое, он ищет оператор
long()
. Если ему
понадобится объект
Foo
, он ищет в классе
Foo
либо конструктор с аргументом
String
, либо
operator Foo()
. Возникает интересный вопрос: если оператор преобразования делает фактически то
же, что и конструктор, почему бы не обойтись чем-нибудь одним? Преимущество конструкторов
состоит в том, что они обеспечивают инкапсуляцию результирующего класса. Чтобы сконструировать
объект другого класса, оператор преобразования должен очень много знать о нем. Вот почему для
перехода от одного типа к другому обычно используются конструкторы. А если осуществляется
переход к базовому типу вроде
int
? Вряд ли вы будете требовать, чтобы компилятор создавал для
int
новые конструкторы, которые знают о существовании ваших пользовательских типов. А если и будете,
то не рискнете признаться в этом вслух. Только оператор преобразования может автоматически
перейти к базовому типу. Даже если результирующий тип не является базовым, он может быть частью
готовой библиотеки, которую нежелательно модифицировать. И снова оператор преобразования
справляется с задачей.
Операторы преобразования можно объявлять для любых типов данных. Они вызываются без
аргументов, а тип возвращаемого значения определяется по имени оператора. Операторы
преобразования, как и все остальные операторы, бывают константными или неконстантными. Часто
определяется как константная, так и неконстантная версии одного оператора. Как правило, константная
версия работает более эффективно, поскольку неконстантная версия обычно выполняет копирование
данных.
class String {
private:
char*
s;
public:
operator const char*() const { return s; }
operator
char*():
};
String::operator char*()
{
char* newStr = new char[strlen(s) + 1];
strcpy(newStr,
s);
return
newStr;
}
Клиентский код, использующий неконстантную версию, должен взять на себя ответственность за
удаление дубликата.
Порядок поиска и неоднозначность
Если во время обработки программы компилятор C++ находит оператор, он выполняет описанные
ниже действия в указанном порядке, чтобы решить, как его компилировать. Описание относится к
бинарному оператору, но та же самая логика используется и для унарных операторов:
1. Если оба аргумента относятся к базовым типам, используется встроенный оператор.
50
2. Если слева указан пользовательский тип, компилятор ищет перепруженный оператор в форме
функции данного класса, подходящей для всей сигнатуры подвыражения оператора. Если такой
оператор будет найден, он используется при компиляции.
3. Если все остальные варианты испробованы, компилятор ищет перегрузку в форме внешней
функции.
Неоднозначность может возникнуть лишь в том случае, если она присутствует в левостороннем классе
или в глобальном пространстве, и никогда — из-за совпадения перегруженных операторов в форме
функции класса и внешней функции. При наличии неоднозначности сообщение об ошибке выдается
лишь после вашей попытки реально использовать оператор. Так компилятор внушает ложное чувство
безопасности и ждет, пока вы утратите бдительность, чтобы огреть вас дубиной по голове.
Виртуальные операторы
Операторы классов можно объявлять виртуальными, как и все остальные функции классов.
Компилятор динамически обрабатывает перегруженный левосторонний оператор, как и любую другую
функцию класса. Такая возможность особенно полезна в ситуациях, когда вы пытаетесь создать
семейство классов, но открываете внешнему миру лишь их общий базовый класс. С точки зрения
синтаксиса все выглядит просто, но логика программы может стать довольно запутанной. Виртуальные
операторы являются одной из важнейших тем части 3, поэтому сейчас мы не будем вдаваться в
подробности.
Оператор ->
Оператор
->
занимает особое место среди операторов. Для начала рассмотрим его базовый синтаксис.
class Pointer {
private:
Foo*
f;
public:
Pointer(Foo* foo) : f(foo) {}
Foo* operator->() const { return f; }
};
Pointer p(new Foo);
p->MemberOfFoo();
В приведенном фрагменте
р
используется для косвенного вызова функции класса
Foo
. Компилятор
интерпретирует любой указатель на структуру или класс (
*
-переменная) как базовый тип
>
, а для всех
базовых типов указателей существует встроенный оператор
->
. Встретив
->
, компилятор смотрит на
левостороннее выражение; если оно представляет собой указатель па структуру или указатель на класс,
для обращения к членам используется встроенный оператор
->
. Если левостороннее выражение
представляет собой пользовательский тип, этот тип должен перегрузить оператор
->
. Перегруженный
вариант должен возвращать либо указатель на структуру/класс, либо какой-нибудь другой
пользовательский тип, который также перегружает оператор
->
. Если возвращаемое значение
относится к пользовательскому типу, компилятор заменяет левостороннее выражение возвращаемым
значением оператора
->
(в нашем примере
Foo*
) и продолжает свои попытки до тех пор, пока не
доберется до встроенного указателя. Таким образом, следующее двухшаговое косвенное обращение
также будет работать.
class Pointer2 {
private:
Pointer
p;
public:
Pointer(Foo* foo) : p(foo) {}
Pointer operator->() const { return p; }
};
Pointer2 p(new Foo);
p->MemberOfFoo();
Do'stlaringiz bilan baham: |