Pregunta

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.

¿Fue útil?

Solución 5

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.

Otros consejos

The whole point of RAII is to NOT assign any resources (like the int-array) to dangling pointers. Instead, use std::vector or assign the array pointer to something like std::unique_ptr. This way the resources will be destroyed as exceptions occur.

And no, you don't have to use STL, but to make use of RAII, the lowest base-resources (like heap allocated arrays) have to be created using RAII as well and the easiest way to do this is to use STL rather than writing your own smart-pointer or vectors.

if exception happen after the new, you have to catch the exception and delete the pointer in the constructor then re-throw in this case, the destructor will not be called since the object is never constructed.

otherwise if i is a std::vector, it will clean up automatically

One way to handle this is to put everything that might be invalidated by an exception into a local variable which itself uses RAII, then assign to your members at the end when it's safe.

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

For more information see Exception Safety.

If you know it throws, put it in a 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();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top