I am having a potentially unstable class that someone else wrote, and I'm having to create an array of objects of that class. I mentioned that the class is unstable, so it may occasionally throw an exception in the default constructor. I don't have access to the source code, only the compiled binaries.
When I am allocating the dynamic array of these type of objects using new
, there is the chance that one of these bad objects may throw an exception. It is throwing a custom exception, not std::bad_alloc
.
Anyway, I need to make the program recover from the exception and just keep on chugging, albeit setting some error flags and what not. I think that I should delete
the memory associated with the array to prevent a memory leak.
My reasoning is that if the class throws an exception constructing an element somewhere in the middle of the array, that element won't be constructed properly, and all the future elements will be stopped constructing by the exception, but the previous elements will have been properly constructed since that happened before the exception was thrown. I am wondering, is it a good idea to call delete
in the catch (...) { }
? How would I go about solving this memory leak?
Badclass* array = nullptr;
try {
array = new Badclass[10]; // May throw exceptions!
} catch (...) {
delete[] array;
array = nullptr;
// set error flags
}
This is the way I visualize this in the memory. Is this correct?
array 0 1 2 3 4 5 6 7 8 9
___ __________________________________
| ---------->| :) | :) | :) | :) | :( | | | | | |
|___| |____|____|____|____|____|_|_|_|_|_|
Dynamically allocated arrays are allocated on the heap at run time. The heap space can be assigned to global or local pointer variables that store the address of the allocated heap space (point to the first bucket).
Dynamic memory allocation in C/C++ refers to performing memory allocation manually by a programmer. Dynamically allocated memory is allocated on Heap, and non-static and local variables get memory allocated on Stack (Refer to Memory Layout C Programs for details).
To answer the final question:
How would I go about solving this memory leak?
There is no memory leak. The leak would only happen if BadClass
itself dynamically allocated content and never freed it in its destructor. Since we're oblivious to your BadClass
implementation, and not in the business of guessing, that's up to you. The only way new BadClass[N];
leaks memory in itself is if it completes and you later toss out the only reference to it you're manually managing (array
).
An array being dynamically allocated, throwing within one of the constructors for elements therein, will (a) back out destructors in opposite order for elements already constructed,(b) free the allocated memory, and finally (c) officiate the actual throw to the nearest catch handler (or the default handler when there is none).
Because the throw happens, the assignment to the resulting array pointer never transpires, and therefore needs no delete[]
.
Best demonstrated by example:
#include <iostream>
struct A
{
static int count;
int n;
A() : n(++count)
{
std::cout << "constructing " << n << '\n';
if (count >= 5)
throw std::runtime_error("oops");
}
~A()
{
std::cout << "destroying " << n << '\n';
}
};
int A::count;
int main()
{
A *ar = nullptr;
try
{
ar = new A[10];
}
catch(std::exception const& ex)
{
std::cerr << ex.what() << '\n';
}
}
Output
constructing 1
constructing 2
constructing 3
constructing 4
constructing 5
destroying 4
destroying 3
destroying 2
destroying 1
oops
Note that because the construction of element '5' never completed, its destructor is not fired. However, members that were successfully constructed are destructed (not demonstrated in the example above, but a fun exercise if you're up for it).
All of that said, use smart pointers regardless.
In the following line of code:
array = new Badclass[10];
new Badclass[10]
is evaluated first. If that throws an exception then execution does not reach the assignment. array
retains its previous value which is nullptr
.
It has no effect to invoke delete on a nullptr
.
The question from the comments section:
Is this kind of behavior based on the same principle as stack unwinding?
The section on "Exception handling" in the standard helps us understand what happens when an exception is thrown at the time of allocation.
18 Exception handling [except]
...
18.2 Constructors and destructors [except.ctor]1.As control passes from the point where an exception is thrown to a handler, destructors are invoked by a process, specified in this subclause, called stack unwinding.
...
3.If the initialization or destruction of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object’s direct subobjects and, for a complete object, virtual base class subobjects, whose initialization has completed and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.
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