Search code examples
c++raii

How to use RAII to acquire resources of class?


There is example that shows that using RAII this way:

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");
}

is safe. but my question is: what if there is exception thrown in p=fopen(n,a); then memory allocated to i is not returned. Is this right to assume that RAII tells you then each time you want X to be safe then all resources acquired by X must be allocated on stack? And if X.a is being created then resources of a must also be placed on stack? and again, and again, I mean finally if there is some resource placed on heap how it could be handled with RAII? If it is not mine class i.e.


Solution

  • Treating this as an intellectual exercise where you don't want to use std::vector, you need to divide your classes up so they have a single responsibility. Here's my "integer array" class. Its responsibility is to manage the memory for an integer array.

    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_;
    };
    

    Here is my file handling class. Its responsibility is to manage a 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_;
    };
    

    Note, that I convert my construction error to an exception; fp_ being a valid file pointer is an invariant that I wish to maintain so I abort construction if I cannot set this invariant up.

    Now, makeing File_ptr exception safe is easy and the class needs no complex resource management.

    class File_ptr {
    private:
        FileHandle p;
        IntArray i; 
    public:
        File_ptr(const char* n, const char* s)
         : p(n, s)
         , i()
        {}
    };
    

    Note the lack of any user-declared destructor, copy assignment operator or copy constructor. I can swap the order of the members and in either case it doesn't matter which constructor throws.