Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can someone explain exactly what happens if an exception is thrown during the process of allocating an array of objects on the heap?

I defined a class foo as follows:

class foo {
private:
   static int objcnt;
public:
   foo() {
       if(objcnt==8)
           throw outOfMemory("No more space!");
       else
          objcnt++;
   }

   class outOfMemory {
   public:
       outOfMemory(char* msg) { cout << msg << endl;}
   };

   ~foo() { cout << "Deleting foo." << endl; objcnt--;}
};
int foo::objcnt = 0;

And here's the main function:

int main() {
    try {
            foo* p = new foo[3];
            cout << "p in try " << p << endl;
            foo* q = new foo[7];
        }catch(foo::outOfMemory& o) {
           cout << "Out-of-memory Exception Caught." << endl;
        }
}

It is obvious that the line "foo* q = new foo[7];" only creates 5 objects successfully, and on the 6th object an Out-of-memory exception is thrown. But it turns out that there's only 5 destructor calls, and destrcutor is not called for the array of 3 objects stored at the position p points to. So I am wondering why? How come the program only calls the destructor for those 5 objects?

like image 338
rialmat Avatar asked Oct 14 '25 18:10

rialmat


1 Answers

The "atomic" C++ allocation and construction functions are correct and exception-safe: If new T; throws, nothing leaks, and if new T[N] throws anywhere along the way, everything that's already been constructed is destroyed. So nothing to worry there.

Now a digression:

What you always must worry about is using more than one new expression in any single unit of responsibility. Basically, you have to consider any new expression as a hot potato that needs to be absorbed by a fully-constructed, responsible guardian object.

Consider new and new[] strictly as library building blocks: You will never use them in high-level user code (perhaps with the exception of a single new in a constructor), and only inside library classes.

To wit:

// BAD:
A * p = new A;
B * q = new B;  // Ouch -- *p may leak if this throws!

// Good:
std::unique_ptr<A> p(new A);
std::unique_ptr<B> q(new B); // who cares if this throws
std::unique_ptr<C[3]> r(new C[3]); // ditto

As another aside: The standard library containers implement a similar behaviour: If you say resize(N) (growing), and an exception occurs during any of the constructions, then all of the already-constructed elements are destroyed. That is, resize(N) either grows the container to the specified size or not at all. (E.g. in GCC 4.6, see the implementation of _M_fill_insert() in bits/vector.tcc for a library version of exception-checked range construction.)

like image 93
Kerrek SB Avatar answered Oct 19 '25 20:10

Kerrek SB



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!