Exceptional C++ mentions the following code
template <class T> class Stack
{
public:
Stack();
~Stack();
/*...*/
private:
T* v_; // ptr to a memory area big
size_t vsize_; // enough for 'vsize_' T's
size_t vused_; // # of T's actually in use
};
template<class T>
Stack<T>::Stack()
: v_(new T[10]), // default allocation
vsize_(10),
vused_(0) // nothing used yet
{
}
It says that If one of the T constructors threw, then any T objects that were fully constructed were properly destroyed and, finally, operator delete was automatically called to release the memory. That makes us leakproof.
My understanding was that if a constructor throws an exception, the application should cleanup any allocated resources. How is the above leakproof?
Quoting the C++03 standard, §5.3.4/8:
A new-expression obtains storage for the object by calling an allocation function. If the new-expression terminates by throwing an exception, it may release storage by calling a deallocation function. If the allocated type is a non-array type, the allocation function’s name is
operator new
and the deallocation function’s name isoperator delete
. If the allocated type is an array type, the allocation function’s name isoperator new[]
and the deallocation function’s name isoperator delete[]
.
§5.3.4/17:
If any part of the object initialization described above terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression.
Consequently, if any T
constructor throws an exception, the runtime will destroy any already-created subobjects of the T
instance whose constructor threw, then call operator delete[]
on the array as a whole, destroying any already-created elements and deallocating the array's memory.
[Correction:] It is not. An exception in your constructor won't leak resources because the only place an exception could occur is inside the new
expression, and if a new
expression fails, the resources that were allocated by it are freed. Your situation is special because you only make one single allocation in the constructor -- in general this is not safe!
Your quoted phrase refers to is the delete operator for the failed-object whose constructor threw:
struct T
{
T() { throw 1; }
char data[200];
};
// in your code:
T * pt = new T;
In the last line, memory is allocated before the constructor is invoked. That memory is released in the event of an exception, by an automatic call to ::operator delete(pt)
. (In general, the matching delete-operator (not "expression"!) matching the new expression is called.)
It goes like this:
Successful construction: 1. Allocation. 2. Construction. 3. Destruction. 4. Deallocation.
Unsuccessful construction: 1. Allocation. 2. Deallocation.
Note that we only have an object after the constructor has completed -- so in the event of an exception in the constructor, we do not even have an object. That's why I said "failed-object" above with a hyphen, because it's not an object at all (like the Douglas-fir isn't a fir at all).
Your code is potentially entirely leak unsafe, if you are making more than one allocation which could throw - i.e. a leak occurs whenever one object has been successfully constructed but another, subsequent one fails. You should probably just not call new
in the initializer list and instead put it in the body:
class Danger
{
T * pt1, * pt2;
public:
Danger()
{
try { pt1 = new T; } catch(...) { throw(); }
try { pt2 = new T; } catch(...) { delete pt1; throw(); }
}
};
Or, by the principle of single responsibility, don't use raw pointers but use resource managing containers that clean up after themselves!!
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