Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Клуб профессиональных программистов :: Выпуск #3




Новости

Создана "Рабочая группа" по работе над Клубом. Сейчас работаем над очисткой сайта от старых бесполезных сообщений и над исправлением некоторых недоработок движка.

В субботу-воскресение успешно прошла очередная встреча членов клуба. Присутствие самого неугомонного - 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.

Если сделать иначе, в результате или будет разорвана цепь последовательных присваиваний, или будут проблемы при неявных преобразованиях типов.

В следующей части мы подробно рассмотрим вопрос – а зачем производить проверку на присваивание самому себе?

Если у вас есть вопросы – пишите, будем разбираться.

Сергей Малышев (aka Михалыч).

Эту статью можно найти на нашем сайте.

Там же можно прочесть начало серии.


А теперь прощаемся с Вами до следующего выпуска.

                                        С уважением, команда Клуба.




В избранное