Чтобы избежать такое фиаско, круто изменяющее вашу жизнь, инициализируйте в списке инициализации ваши указатели значением NULLи затем создавайте в конструкторе объекты, использующие динамическую память. В этом случае будет легче перехватывать любую исключительную ситуацию и выполнять подчистку, поскольку допускается использовать оператор deleteдля NULL-указателей.
BrokerBetter(int devno1, int devno2) :
dev1_(NULL), dev2_(NULL) {
try {
dev1_ = new Device(devno1);
dev2_ = new Device(devno2);
} catch (...) {
delete dev1_; // Это сработает в любом случае
throw;
}
}
Итак, вышесказанное можно подытожить следующим образом: если вам необходимо использовать члены-указатели, инициализируйте их значением NULLв списке инициализации и затем выделяйте в конструкторе память для соответствующих объектов, используя блок try/catch. Вы можете освободить любую память в catch-обработчике. Однако, если допускается работа с автоматическими членами, сконструируйте их в списке инициализации и используйте специальный синтаксис блока try/catchдля обработки любых исключений.
Смотри также
Рецепт 9.2.
9.4. Создание безопасных при исключениях функций-членов
Проблема
Создается функция-член и необходимо обеспечить базовые и строгие гарантии ее безопасности при исключениях, а именно отсутствие утечки ресурсов и то, что объект не будет иметь недопустимое состояние в том случае, если выбрасывается исключение.
Решение
Необходимо выяснить, какие операции могут выбрасывать исключения, и следует выполнить их первыми, обычно заключая в блок try/catch. После того как будет выполнен программный код, который может выбрасывать исключение, вы можете изменять состояние объектов. В примере 9.4 показан один из способов обеспечения безопасности функции-члена при исключениях.
Пример 9.4. Безопасная при исключениях функция-член
class Message {
public:
Message(int bufSize = DEFAULT_BUF_SIZE) :
bufSize_(bufSize), initBufSize_(bufSize), msgSize_(0), buf_(NULL) {
buf_ = new char[bufSize];
}
~Message() {
delete[] buf_;
}
// Добавить в конец символьные данные
void appendData(int len, const char* data) {
if (msgSize_+len > MAX_SIZE) {
throw out_of_range("Data size exceeds maximum size.");
}
if (msgSize_+len > bufSize_) {
int newBufSize = bufSize_;
while ((newBufSize *= 2) < msgSize_+len);
char* p = new char[newBufSize]; // Выделить память
// для нового буфера
copy(buf_, buf_+msgSize_, p); // Скопировать старые данные
copy(data, data+len, p+msgSize_); // Скопировать новые данные
msgSize_ += len;
bufSize_ = newBufSize;
delete[] buf_; // Освободись старый буфер и установить указатель на
buf_ = p; // новый буфер
} else {
copy(data, data+len, buf_+msgSize_);
msgSize_ += len;
}
}
// Скопировать данные в буфер вызывающей программы
int getData(int maxLen, char* data) {
if (maxLen < msgSize_) {
throw out_of_range("This data is too big for your buffer.");
}
copy(buf_, buf_+msgSize_, data);
return(msgSize_);
}
private:
Message(const Message& orig) {} // Мы рассмотрим эти операторы
Message& operator=(const Message& rhs) {} // в рецепте 9.5
int bufSize_;
int initBufSize_;
int msgSize_;
char* buf_;
};
Обсуждение
Представленный в примере 9.4 класс Messageявляется классом, содержащим символьные данные; вы могли бы использовать его в качестве оболочки текстовых или бинарных данных, которые передаются из одной системы в другую. Здесь нас интересует функция-член appendData, которая добавляет данные, переданные вызывающей программой, в конец данных, уже находящихся в буфере, причем увеличивая при необходимости размер буфера. Здесь обеспечивается строгая гарантия безопасности этой функции-члена при исключениях, хотя на первый взгляд может быть не совсем понятно, чем это достигается.
Рассмотрим следующий фрагмент appendData.
if (msgSize_+len > bufSize_) {
int newBufSize = bufSize_;
while ((newBufSize *= 2) < msgSize_+len);
char* p = new char[newBufSize];
Этот блок программного кода обеспечивает увеличение размера буфера. Я его увеличиваю путем удвоения его размера до тех пор, пока он не станет достаточно большим. Этот фрагмент программного кода безопасен, потому что исключение может быть выброшено здесь только при выполнении оператора new, и я не обновляю состояние объекта и не выделяю память ни под какие другие ресурсы до завершения его выполнения. Этот оператор выбросит исключение bad_alloc, если операционная система не сможет выделить участок памяти необходимого размера.
Читать дальше