Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ | страница 50
>{
>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