Все вызовы функции swap() обходятся без квалификаторов. Таким образом, каждый вызов должен выглядеть как swap(), а не std::swap(). По причинам, рассматриваемым в разделе 16.3, если есть специфическая для типа версия функции swap(), она будет лучшим соответствием, чем таковая из пространства имен std. В результате, если у типа есть специфическая версия функции swap(), вызов swap() будет распознан как относящийся к специфической версии. Если специфической для типа версии нет, то (с учетом объявления using для функции swap() в области видимости) при вызове swap() будет использована версия из пространства имен std.

У очень осторожных читателей может возникнуть вопрос: почему объявление using функции swap() не скрывает объявление функции swap() класса HasPtr (см. раздел 6.4.1). Причины, по которым работает этот код, объясняются в разделе 18.2.3.

Использование функции swap() в операторах присвоения

Классы, определяющие функцию swap(), зачастую используют ее в определении собственного оператора присвоения. Эти операторы используют технологию, известную как копия и обмен (copy and swap)). Она подразумевает обмен левого операнда с копией правого:

// обратите внимание: параметр rhs передается по значению. Это значит,

// что конструктор копий класса HasPtr копирует строку в правый

// операнд rhs

HasPtr& HasPtr::operator=(HasPtr rhs) {

 // обменивает содержимое левого операнда с локальной переменной rhs

 swap(*this, rhs); // теперь rhs указывает на память, которую

                   // использовал этот объект

 return *this; // удаление rhs приводит к удалению указателя в rhs

}

В этой версии оператора присвоения параметр не является ссылкой. Вместо этого правый операнд передается по значению. Таким образом, rhs — это копия правого операнда. Копирование объекта класса HasPtr приводит к резервированию новой копии строки данного объекта.

В теле оператора присвоения вызывается функция swap(), обменивающая переменные-члены rhs с таковыми в *this. Этот вызов помещает указатель, который был в левом операнде, в rhs, и указатель, который был в rhs,— в *this. Таким образом, после вызова функции swap() указатель-член в *this указывает на недавно зарезервированную строку, являющуюся копией правого операнда.

По завершении оператора присвоения параметр rhs удаляется и выполняется деструктор класса HasPtr. Этот деструктор освобождает память, на которую теперь указывает rhs, освобождая таким образом память, на которую указывал левый операнд.

В этой технологии интересен тот момент, что она автоматически отрабатывает присвоение себя себе и изначально устойчива к исключениям. Копирование правого операнда до изменения левого отрабатывает присвоение себя себе аналогично примененному в нашем первоначальном операторе присвоения (см. раздел 13.2.1). Это обеспечивает устойчивость к исключениям таким же образом, как и в оригинальном определении. Единственный код, способный передать исключение, — это оператор new в конструкторе копий. Если исключение произойдет, то это случится прежде, чем изменится левый операнд.

Операторы присвоения, использующие копию и обмен, автоматически устойчивы к исключениям и правильно отрабатывают присвоение себя себе.

Упражнения раздела 13.3

Упражнение 13.29. Объясните, почему вызов функции swap() в вызове swap(HasPtr&, HasPtr&) не приводит к бесконечной рекурсии.

Упражнение 13.30. Напишите и проверьте функцию swap() для подобной значению версии класса HasPtr. Снабдите свою функцию swap() оператором вывода примечания о ее выполнении.

Упражнение 13.31. Снабдите свой класс оператором < и определите вектор объектов класса HasPtr. Вставьте в вектор несколько элементов, а затем отсортируйте его (sort()). Обратите внимание на то, когда вызывается функция swap().

Упражнение 13.32. Получит ли преимущества подобная указателю версия класса HasPtr от определения собственной функции swap()? Если да, то в чем это преимущество? Если нет, то почему?

<p>13.4. Пример управления копированием</p>

Несмотря на то что управление копированием обычно необходимо для классов, резервирующих ресурсы, управление ресурсами не единственная причина определения этих функций-членов. У некоторых классов может быть необходимость в учете или других действиях, выполняемых функциями управления копированием.

Перейти на страницу:

Похожие книги