Как использовать 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();
}