Правило 11: В operator= осуществляйте проверку на присваивание самому себе
Правило 11: В operator= осуществляйте проверку на присваивание самому себе
Присваивание самому себе возникает примерно в такой ситуации:
class Widget {...};
Widget w;
...
w = w; // присваивание себе
Код выглядит достаточно нелепо, однако он совершенно корректен, и в том, что программисты на такое способны, вы можете не сомневаться ни секунды.
Кроме того, присваивание самому себе не всегда так легко узнаваемо. Например:
a[i] = a[j]; // потенциальное присваивание себе
это присваивание себе, если i и j равны одному и тому же значению, и
*px = *py; // потенциальное присваивание себе
тоже становится присваиванием самому себе, если окажется, что px и py указывают на одно и то же.
Эти менее очевидные случаи присваивания себе являются результатом совмещения имен (aliasing), когда для ссылки на объект существует более одного способа. Вообще, программа, которая оперирует ссылками или указателями на различные объекты одного и того же типа, должна считаться с тем, что эти объекты могут совпадать. Необязательно даже, чтобы два объекта имели одинаковый тип, ведь если они принадлежат к одной иерархии классов, то ссылка или указатель на базовый класс может в действительно относиться к объекту производного класса:
class Base {...};
class Derived: public Base {...};
void doSomething(const Base& rb, // rb и *pd могут быть одним и тем же
Derived * pd); // объектом
Если вы следуете правилам 13 и 14, то всегда пользуетесь объектами для управления ресурсами; следите за тем, чтобы управляющие объекты правильно вели себя при копировании. В таком случае операторы присваивания должны быть безопасны относительно присваивания самому себе. Если вы пытаетесь управлять ресурсами самостоятельно (а как же иначе, если вы пишете класс для управления ресурсами), то можете попасть в ловушку, нечаянно освободив ресурс до его использования. Например, предположим, что вы создали класс, который содержит указатель на динамически распределенный объект класса Bitmap:
class Bitmap {...};
class Widget {
...
private:
Bitmap *pb; // указатель на объект, размещенный в куче
};
Ниже приведена реализация оператора присваивания operator=, которая выглядит совершенно нормально, но становится опасной в случае выполнения присваивания самому себе (она также небезопасна с точки зрения исключений, но сейчас не об этом).
Widget&
Widget::operator=(const Widget& rhs) // небезопасная реализация operator=
{
delete pb; // прекратить использование текущего
// объекта Bitmap
pb = new Bitmap(*rhs.pb); // начать использование копии объекта
// Bitmap, указанной в правой части
return *this; // см. правило 10
}
Проблема состоит в том, что внутри operator= *this (чему присваивается значение) и rhs (что присваивается) могут оказаться одним и тем же объектом. Если это случится, то delete уничтожит не только Bitmap, принадлежащий текущему объекту, но и Bitmap, принадлежащий объекту в правой части. По завершении работы этой функции Widget, который не должен был бы измениться в процессе присваивания самому себе, содержит указатель на удаленный объект!
Традиционный способ предотвратить эту ошибку состоит в том, что нужно выполнить проверку совпадения в начале operator=:
Widget&
Widget::operator=(const Widget& rhs) // небезопасная реализация operator=
{
if(this == &rhs) return *this; // проверка совпадения: если
// присваивание самому себе, то
// ничего не делать
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
Это решает проблему, но я уже упоминал, что предыдущая версия оператора присваивания была не только опасна в случае присваивания себе, но и небезопасна в смысле исключений, и последняя опасность остается актуальной во второй версии. В частности, если выражение «new Bitmap» вызовет исключение (либо по причине недостатка свободной памяти, либо исключение возбудит конструктор копирования Bitmap), то Widget также будет содержать указатель на несуществующий Bitmap. Такие указатели – источник неприятностей. Их нельзя безопасно удалить, их даже нельзя разыменовывать. А вот потратить массу времени на отладку, выясняя, откуда они взялись, – это можно.
К счастью, существует способ одновременно сделать operator= безопасным в смысле исключений и безопасным по части присваивания самому себе. Поэтому все чаще программисты не занимаются специально присваиванием самому себе, а сосредоточивают усилия на достижении безопасности в смысле исключений. В правиле 29 эта проблема рассмотрена детально, а сейчас достаточно упомянуть, что во многих случаях продуманная последовательность операторов присваивания может обеспечить безопасность в смысле исключений (а заодно безопасность присваивания самому себе) кода. Например, ниже мы просто не удаляем pb до тех пор, пока не скопируем то, на что он указывает:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; // запомнить исходный pb
pb = new Bitmap(*rhs.pb); // установить указатель pb на копию *pb
delete pOrig; // удалить исходный pb
return *this;
}
Теперь, если «new Bitmap» возбудит исключение, то pb (и объект Widget, которому он принадлежит) останется неизменным. Даже без проверки на совпадение здесь обрабатывается присваивание самому себе, потому что мы сделали копию исходного объекта Bitmap, удалили его, а затем направили указатель на сделанную копию. Возможно, это не самый эффективный способ обработать присваивание самому себе, но он работает.
Если вы печетесь об эффективности, то можете вернуть проверку на совпадение в начале функции. Но прежде спросите себя, как часто может происходить присваивание самому себе, потому что выполнение проверки тоже не обходится даром. Это делает код (исходный и объектный) чуть больше, а ветвление несколько снижает скорость исполнения. Эффективность предварительной выборки команд, кэширования и конвейеризации тоже может пострадать.
Альтернативой ручному упорядочиванию предложений в operator= может быть обеспечение и безопасности в смысле исключений, и безопасности присваивания самому себе за счет применения техники «копирования с обменом» («copy and swap»). Она тесно связана с безопасностью в смысле исключений, поэтому рассматривается в правиле 29. Тем не менее это достаточно распространенный способ написания operator=, и на него стоит взглянуть:
class Widget {
...
void swap(Widget& rhs); // обмен данными *this и rhs
... // см. подробности в правиле 29
};
Widget& Widget:: operator=(const Widget& rhs)
{
Widget temp(rhs); // создать копию данных rhs
swap(tmp); // обменять данные *this с копией
return *this;
}
Здесь мы пользуемся тем, что: (1) оператор присваивания можно объявить как принимающим аргумент по значению и (2) передача объекта по значению означает создание копии этого объекта (см. правило 20):
Widget& Widget::operator=(Widget rhs) // rhs – копия переданного объекта
{ // обратите внимание на передачу по
// значению
swap(rhs); // обменять данные *this с копией
return *this;
}
Лично меня беспокоит, что такой подход приносит ясность в жертву изощренности, но, перемещая операцию копирования из тела функции в конструирование параметра, компилятор иногда может сгенерировать более эффективный код.
Что следует помнить
• Убедитесь, что operator= правильно ведет себя, когда объект присваивается самому себе. Для этого можно сравнить адреса исходного и целевого объектов, аккуратно упорядочить предложения или применить идиому копирования обменом.
• Убедитесь, что все функции, оперирующие более чем одним объектом, ведут себя корректно при совпадении двух или более объектов.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
Сам себе СМИ
Сам себе СМИ Метки: СМИ, пользовательский контентЖурнал Time в 2006 году присвоил звание «Человек года» интернет-пользователю как таковому, вынеся на свою обложку зеркальный монитор с надписью You, в котором отражается тот, кто взял в руки журнал. Редакция Time тем самым хотела
Глава 15. Если некогда вести самому
Глава 15. Если некогда вести самому Понятно, что многие владельцы проектов не смогут вести их сами. Так или иначе, многим придется столкнуться с наймом сотрудников. Какова сейчас ситуация на рынке?Если вы будете публиковать не уникальный контент, ситуация самая
IP Operator
IP Operator Программа IP Operator так же, как и предыдущая, предназначена для создания и настройки сетевых профилей. Данным приложением комплектуются ноутбуки производства компании LG. Любой желающий может также бесплатно скачать эту программу с сайта LG. Она не привязывается к
1.6.7. Правило прозрачности: для того чтобы упростить проверку и отладку программы, ее конструкция должна быть обозримой
1.6.7. Правило прозрачности: для того чтобы упростить проверку и отладку программы, ее конструкция должна быть обозримой Поскольку отладка часто занимает три четверти или более времени разработки, раннее окончание работы в целях облегчения отладки может быть очень
1.6.7. Правило прозрачности: для того чтобы упростить проверку и отладку программы, ее конструкция должна быть обозримой
1.6.7. Правило прозрачности: для того чтобы упростить проверку и отладку программы, ее конструкция должна быть обозримой Поскольку отладка часто занимает три четверти или более времени разработки, раннее окончание работы в целях облегчения отладки может быть очень
Шаг 13 - Перегрузка operator+.
Шаг 13 - Перегрузка operator+. Оператор operator-› мы уже перегружали. Результаты получились просто феерические. Давайте замучаем еще кого-нибудь и посмотрим, что получится? Давайте. Первейшим кандидатом на переопределение является оператор operator+,потому что в жизни (помимо С++) он
Массивы и оператор operator[].
Массивы и оператор operator[]. Давайте попробуем придумать класс, объекты которого вели бы себя как массивы? Поехали. Решим, что класс внутри себя должен иметь для простоты массив, ну там счетчик элементов… вроде больше нечему там быть. Ну раз так, то возьмем стек из Шага 13, для
Совет 42. Следите за тем, чтобы конструкция less<T> означала operator<
Совет 42. Следите за тем, чтобы конструкция less<T> означала operator< Допустим, объект класса Widget обладает атрибутами weight и maxSpeed:class Widget { public:size_t weight() const;size_t maxSpeed() const;}Будем считать, что естественная сортировка объектов Widget осуществляется по атрибуту weight, что отражено в
Заведите себе врага
Заведите себе врага БоритесьИногда лучший способ узнать, каким должно быть ваше приложение — это узнать, каким оно не должно быть. Пусть это будет врагом вашего приложения, и вы будете видеть свет, на который вы должны идти.Когда мы решили создать систему управления
ПИСЬМОНОСЕЦ: Сам себе «Компьютерру»
ПИСЬМОНОСЕЦ: Сам себе «Компьютерру» Автор: Сергей ВильяновВот Евгений Антонович обрадовался, получив камеру в подарок. Приятно. А своему другу-то чего приятное не стал делать? Взял бы и подарил. Была бы дружба крепче.И еще непонятна фраза «ушел в руки друга по цене,
IP Operator
IP Operator Программа IP Operator производства компании LG распространяется с ноутбуками LG и, возможно, с некоторыми другими. Скорее всего, данная программа будет работать и на ноутбуках, произведенных другими компаниями (мы не проверяли). Назначение данной программы такое же, как и
Обучиться самому (или обеспечить обучение сотрудников)
Обучиться самому (или обеспечить обучение сотрудников) Даже когда процедуры реагирования на инцидент имеются, системные администраторы и пользователи могут быть не обучены их применению. Политики и процедуры, которые не были ясно усвоены, не принесут много пользы. При
Учить или учиться самому сколько нужно
Учить или учиться самому сколько нужно Чтобы система безопасности заработала, каждому сотруднику необходимо знать основные правила ее работы. Хотя они знают правила, все же никому не повредит, если вы будете предлагать им эти правила соблюдать. Используйте электронную