Создана "Рабочая группа" по работе над Клубом. Сейчас работаем над очисткой сайта от старых бесполезных сообщений и над исправлением некоторых недоработок движка.
В субботу-воскресение успешно прошла очередная встреча членов клуба. Присутствие самого неугомонного - Pu - гарантировало тяжелое похмелие. Тут впечатления и фотоматериалы.
Выложен на сервер архива любительский фильм одного знакомого Pu, увлекающегося автостопом. Ссылку можно найти здесь.
Кстати, сам Pu прибыл в Москву из Питера именно автостопом. Обратно, из-за нехватки времени, ехал уже поездом.
Alex1153 написал сетевую игру Реверси. Предлогает потестировать.
Продолжаем публикацию отрывков статей Михалыча. Сегодня - "operator= Рассмотрим подробно. Часть 1.".
operator= Рассмотрим подробно. Часть 1. Он возвращает ссылку на *this. А почему
В серии статей, объединенных общим названием "Классы. Копирование и присваивание" тема оператора присваивания уже рассматривалась. В частности, там упоминалось о том, что в этом операторе нужно производить проверку на присваивание самому себе, что оператор должен возвращать ссылку на *this, и еще некоторые основные моменты. Все это, конечно, необходимо знать для того, чтобы правильно написать операцию присваивания. Однако, "за бортом" осталось подробное рассмотрение вопросов "зачем"
и "почему" необходимо делать то, или иное в операторе присваивания.
Если у вас есть желание понять (по возможности более полно) тонкости применения operator=, то этот материал, несомненно, для вас.
Сразу скажу, если в вашей домашней библиотеке есть книга Скотта Мейерса "Эффективное использование С++" (издательство ДМК, Москва 2000), то всю эту информацию можно найти в ней. Если нет – читайте смело дальше. Все идеи, что тут излагаются, навеяны именно этой книгой, "пережеваны" и поданы может быть в более простом для усвоения стиле.
Он возвращает ссылку на *this. А почему?
Для начала давайте взглянем на встроенные типы данных. Тут все просто, понятно и приятно.
float a, b, c;
a = b = c = 3.14;
Для всех встроенных типов допускается применение последовательного присваивания. А, собственно, чем типы, определяемые пользователем хуже встраиваемых? Желательно, чтобы в применении они ни чем не отличались от встроенных (в идеальном случае, конечно, что иногда очень трудно). Значит и в нашем случае необходимо, чтобы для типов, которые мы сами придумаем, так же точно можно было применять последовательное присваивание.
Для примера возьмем уже известный вам (по статьям "Классы. Копирование и присваивание") класс POINT, описывающий точку на плоскости.
class POINT
{
public:
POINT() { X=0; Y=0; } //конструктор по умолчанию
POINT(int a, int b) { X=a; Y=b; } //еще конструктор
… //остальное пока опустим за ненадобностью
POINT& operator=(const POINT&);
int X; //координаты точки
int Y;
};
Теперь произведем последовательное присваивание:
POINT A(2,3); //точка с известными координатами
POINT b, c, d;
b=c=d=A;
Поскольку оператор присваивания ассоциативен, то цепочку присваивания можно представить следующим образом:
b=(c=(d=A));
А теперь посмотрим, как это будет выглядеть в эквивалентной функциональной форме:
b.operator=(c.operator=(d.operator=(A)));
Отсюда прекрасно видно, что аргументом для operator= является значение, которое возвращает предыдущий вызов функции operator=. Значит, тип, который возвращает функция operator=, должен быть таким же, как тип аргумента. Отсюда и нотация записи операции присваивания в общем виде:
X& operator=(const X&);
В принципе, можно возвращать значение типа void. Это будет работать, но не даст вам возможности проводить последовательное присваивание.
Какой же из двух объектов следует использовать в качестве возвращаемого? Тот, который стоит слева от знака присваивания и указывается при помощи указателя this, или объект, который стоит с правой стороны и содержится в списке аргументов?
Вот эти два варианта записи operator=.
POINT& POINT::operator=(const POINT& rhs)
{
… //тут остальное
return *this; // возврат ссылки на объект слева
}
POINT& POINT::operator=(const POINT& rhs)
{
… //тут остальное
return rhs; // возврат ссылки на объект справа
}
Сразу скажем, что версия, которая возвращает rhs, просто не будет компилироваться, будет выдаваться следующая ошибка:
Error: Reference initialized with 'const POINT', needs lvalue of type 'POINT'
Это происходит потому, что аргумент rhs – это ссылка на const POINT, а operator= возвращает ссылку на POINT. А для объекта, объявленного с const нельзя возвращать неконстантную ссылку.
Такую ситуацию можно было бы обойти следующим образом:
POINT& POINT::operator=(POINT& rhs) {…}
То есть просто передать неконстантный аргумент. Для класса POINT этот трюк вполне пройдет. Но зато в других случаях это будет абсолютно недопустимо. Когда будет происходить неявное преобразование типов – начнутся проблемы. Классический пример – это строковый класс типа String. Смотрите:
String S;
S=“This is S”; //что аналогично такому S.operator=(“This is S”)
Аргумент справа имеет тип char[], а вовсе не String. В таком случае неявного преобразования типов компилятор создаст временный объект типа String (с помощью его конструктора) для передачи в качестве аргумента. Но компилятор всегда создает временные объекты как const, поскольку это предотвращает случайную передачу временного объекта в функцию, которая модифицирует аргументы. Такой вариант просто не должен компилироваться, так как произойдет попытка передать объект с const в функцию operator=, у которой соответствующий
аргумент был объявлен без const.
Вот и все варианты! Выбора больше нет. Остается только возвращать ссылку на левосторонний аргумент *this.
Если сделать иначе, в результате или будет разорвана цепь последовательных присваиваний, или будут проблемы при неявных преобразованиях типов.
В следующей части мы подробно рассмотрим вопрос – а зачем производить проверку на присваивание самому себе?
Если у вас есть вопросы – пишите, будем разбираться.