2012-03-26 5 views
1

Пройдя через эффективный C++ (автор Scott Meyers), я натолкнулся на следующий код, который автор использует для иллюстрации того, как следует обрабатывать исключения при копировании элементов данных из одного объекта в другой ,Обработка исключений в операторе присваивания копии (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; 
} 

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

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

ответ

2

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

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

+0

Понравилось, спасибо. – Sam

0

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

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

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

0

В случае, если «новый битмап» выдает исключение, 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 
} 

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

Что касается вашего кода, это не исключение.

Widget& Widget::operator=(const Widget& rhs) 
{ 
    ... 
    delete pb; 
    pb = new Bitmap(*rhs.pb); 
    ... 
} 

После того, как вы освободили память, связанную с 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; 
} 
Смежные вопросы