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?
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With