Как использовать RAII для приобретения ресурсов класса?
есть пример, который показывает, что использование RAII таким образом:
class File_ptr{
//...
File* p;
int* i;
public:
File_ptr(const char* n, const char* s){
i=new int[100];
p=fopen(n,a); // imagine fopen might throws
}
~File_ptr(){fclose(p);}
}
void use_file(const char* fn){
File_ptr(fn,"r");
}
безопасно. но мой вопрос: что, если есть исключение, брошенное в p=fopen(n,a);
затем память, выделенная i
не возвращены. Имеет ли это право предполагать, что RAII
говорит вам, то каждый раз, когда вы хотите X
чтобы быть в безопасности, то все ресурсы, полученные X
должен быть выделен на стеке? И если X.a
создается тогда ресурсы a
также должны быть размещены в стеке? и снова, и снова, я имею в виду наконец, если есть какой-то ресурс, помещенный в кучу, как он может быть обработан с RAII? Если это не мой класс, т. е.
6 ответов
рассматривая это как интеллектуальное упражнение, где вы не хотите использовать std::vector
, вам нужно разделить свои классы, чтобы у них была одна ответственность. Вот мой класс "integer array". Его ответственность заключается в управлении памятью для массива integer.
class IntArray {
public:
IntArray() : ptr_(new int[100]) {}
~IntArray() { delete[] ptr_; }
IntArray(const IntArray&) = delete; // making copyable == exercise for reader
IntArray& operator=(const IntArray&) = delete;
// TODO: accessor?
private:
int* ptr_;
};
вот мой класс обработки файлов. Его ответственность заключается в управлении FILE*
.
class FileHandle {
public:
FileHandle(const char* name, const char* mode)
: fp_(fopen(name, mode))
{
if (fp_ == 0)
throw std::runtime_error("Failed to open file");
}
~FileHandle() {
fclose(fp_); // squelch errors
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// TODO: accessor?
private:
FILE* fp_;
};
обратите внимание, что я преобразую свою ошибку в исключение;fp_
быть допустимым указателем файла является инвариант, который я хочу сохранить, поэтому я прерываю построение, если я не могу установить этот инвариант.
теперь, makeing File_ptr
exception safe легко, и класс не нуждается в сложном управлении ресурсами.
class File_ptr {
private:
FileHandle p;
IntArray i;
public:
File_ptr(const char* n, const char* s)
: p(n, s)
, i()
{}
};
обратите внимание на отсутствие любого объявленного пользователем деструктора, оператора присваивания копии или конструктора копирования. Я могу поменять порядок членов, и в любом случае не имеет значения, какой конструктор бросает.
весь смысл RAII заключается в том, чтобы не назначать никаких ресурсов (например,int
-array) к болтающимся указателям. Вместо этого используйте std::vector
или назначьте указатель массива чему-то вроде std::unique_ptr
. Таким образом, ресурсы будут уничтожены при возникновении исключений.
и нет, вам не нужно использовать STL, но чтобы использовать RAII, самые низкие базовые ресурсы (например, выделенные массивы кучи) должны быть созданы с использованием RAII, а самый простой способ сделать это-использовать STL, а не писать свои собственные smart-указатель или векторы.
Если исключение произойдет после new
, вы должны поймать исключение и удалить указатель в конструкторе, то повторно вызвать в этом случае деструктор не будет вызван, так как объект не построен.
в противном случае, если i
является std:: vector, он будет автоматически очищаться
один из способов справиться с этим-поместить все, что может быть признано недействительным исключением, в локальную переменную, которая сама использует RAII, а затем назначить своим членам в конце, когда это безопасно.
class File_ptr{
//...
File* p;
int* i;
public:
File_ptr(const char* n, const char* s) i(NULL), p(NULL) {
unique_ptr<int> temp_i=new int[100]; // might throw std::bad_alloc
p=fopen(n,a); // imagine fopen might throws
// possibility of throwing an exception is over, safe to set members now
i = temp_i.release();
}
~File_ptr(){fclose(p);}
}
для получения дополнительной информации см. Исключение Безопасности.
Если вы знаете, что он бросает, поместите его в try-catch
.
File_ptr(const char* n, const char* s) {
i=new int[100];
try {
p=fopen(n,a); // imagine fopen might throws
} catch(...) {
delete[] i;
throw;
}
}
File_ptr(const char* n, const char* s)
{
std::unique_ptr<int[]> sp(new int[100]);
p = fopen(n, s);
i = sp.release();
}