обработка исключений в операторе присваивания копии (c ++) - PullRequest
1 голос
/ 26 марта 2012

Проходя через Effective C ++ (автор Скотт Мейерс), я наткнулся на следующий код, который автор использует для иллюстрации того, как следует обрабатывать исключения при копировании элементов данных из одного объекта в другой.

class Bitmap { ... };

class Widget {
  ...

private:
  Bitmap *pb;                                     // ptr to a heap-allocated object
};

Widget& Widget::operator=(const Widget& rhs)
{
  Bitmap *pOrig = pb;               // remember original pb
  pb = new Bitmap(*rhs.pb);         // make pb point to a copy of *pb
  delete pOrig;                     // delete the original pb
  return *this;
}

В случае, если «новое растровое изображение» выдает исключение, pb останется неизменным.Однако, удалив pOrig, память, на которую указывает pb, была освобождена.Разве это не опасно?Чем он лучше, чем следующий код

Widget& Widget::operator=(const Widget& rhs)

{
  if (this == &rhs) return *this;   // identity test: if a self-assignment,
                                    // do nothing
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

Что (он утверждает) является плохим, потому что, когда «новое растровое изображение» выдает исключение (либо из-за недостатка памяти для выделения, либо из-за того, что конструктор копирования растрового изображения выдаетво-первых, виджет будет содержать указатель на удаленное растровое изображение

Я проверил ошибки в книге, но не нашел упоминания об этом примере.Я что-то упускаю из виду?Кроме того, кто-то может предложить лучший способ обработки этого исключения?

Ответы [ 3 ]

2 голосов
/ 26 марта 2012

delete pOrig; будет выполняться тогда и только тогда, когда pb = new Bitmap(*rhs.pb); будет успешным.Если распределение завершится неудачно, то этот ctor больше не будет выполняться вообще - вместо этого стек будет размотан, и выполнение будет выполняться из любой части конструктора Bitmap, которая вызвала исключение, непосредственно в обработчик для любого исключения, которое быловыброшены.Единственной остановкой на этом пути будет уничтожение переменных, локальных для ctor, но поскольку единственная локальная переменная - это указатель, уничтожение - это в значительной степени недопустимый случай.

В случае, если объект Widget содержал какие-либо другиеПеременные-члены, любые из тех, которые были полностью построены, также будут уничтожены как часть раскручивания стека, но, поскольку они не имеют (показано), что здесь не имеет значения.

0 голосов
/ 26 марта 2012

В случае, если «новое растровое изображение» выдает исключение, pb останется неизменным.Однако, удалив pOrig, память, на которую указывает pb, была освобождена.Разве это не опасно?

Нет, вы сделали неверное предположение об удалении pOrig и, возможно, об исключениях.В исходном коде:

Widget& Widget::operator=(const Widget& rhs)
{
  Bitmap *pOrig = pb; // <-- this can't throw
  pb = new Bitmap(*rhs.pb); // <-- this can throw
  delete pOrig; // <-- this can't throw
  return *this; // <-- this can't throw
}

Звонок operator new - это единственное место, где код может скинуть.Если это так, pb не будет присвоен результат.Он будет указывать на предыдущее растровое изображение, и класс останется в допустимом состоянии.Он также не будет продолжать удалять pOrig pointee.В результате, если выдается исключение, утечки нет, и класс остается в допустимом состоянии.

Что касается вашего кода, он не является безопасным для исключения.вы освободили память, связанную с pb, вы поместили класс в недопустимое состояние.Поэтому опасно бросать, пока вы не вернете класс в правильное состояние.Если operator new выбрасывает здесь, вы облажались, и ваш класс Widget оставлен в недопустимом состоянии в результате того, что pb является висящим указателем.Это было бы, как если бы вы вообще не выполняли вторую строку.

Сделайте себе одолжение и избавьте себя от боли в сердце.Используйте RAII и умные указатели, и это будет намного проще.

Widget& Widget::operator=(const Widget& rhs)
{
  unique_ptr<Bitmap> new_bitmap(new Bitmap(*rhs.pb));
  pb.swap(new_bitmap); // make pb a unique_ptr as well
  return *this;
}
0 голосов
/ 26 марта 2012

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

Во втором примере это не проходит, потому что delete изменяет данные записи, за которым следует оператор new, который может выбросить, оставляя данные в неполном состоянии.

В первом примере такой проблемы нет, так как ни один из данных не был изменен до операции new, которая могла бы сгенерировать. Нет никакой возможности, что данные останутся в неполном состоянии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...