18

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
 ___          __________________________________
| ---------->| :) | :) | :) | :) | :( | | | | | |
|___|        |____|____|____|____|____|_|_|_|_|_|
Galaxy
  • 2,363
  • 2
  • 25
  • 59
  • 7
    If `new` throws, `array` was never assigned, so there is nothing for you to `delete`. So even if the exception comes from constructing one of the elements, the language guarantees memory is released. – BoBTFish Oct 16 '18 at 06:55
  • 1
    @BoBTFish The thing is, `new` is not doing the throwing, `Badclass::Badclass()` is! – Galaxy Oct 16 '18 at 06:56
  • 1
    Nevertheless, `new` is the thing that calls `Badclass::Badclass()`, so `new` never completes – Michael Veksler Oct 16 '18 at 06:57
  • 1
    But suppose at least one `Badclass` object is dynamically created without any issues. What happens to it? – Galaxy Oct 16 '18 at 06:59
  • 3
    @Galaxy it doesn't matter. If the requested operation (allocate and construct this sequence of things) throws, everything done to that point related to those two activities is wound back. That includes both the allocation, and any objects that were successfully constructed (and array request of ten objects fulfilling five, then throwing, will fire destructors for the five that constructed successfully, in reverse order of construction, before relinquishing the memory itself and officiating the throw). – WhozCraig Oct 16 '18 at 07:00
  • 1
    @WhozCraig Is this kind of behavior based on the same principle as stack unwinding? Does this also apply to the heap as well? – Galaxy Oct 16 '18 at 07:02
  • 3
    Its based on success of requested operation. The request could not be fulfilled. Therefore, whatever could be is undone and the exception is thrown. That includes the destruction of already-constructed elements. Not that anyone would do any of this (you would likely just use a `std::vector` and be done with it, or have a darn good reason not to). – WhozCraig Oct 16 '18 at 07:07
  • 1
    Possible duplicate of [new\[\] / delete\[\] and throwing constructors / destructors in C++](https://stackoverflow.com/questions/45148674/new-delete-and-throwing-constructors-destructors-in-c) – Daniel Langr Oct 16 '18 at 07:56
  • Refer to [Who deletes the memory allocated during a “new” operation which has exception in constructor?](https://stackoverflow.com/questions/1674980/who-deletes-the-memory-allocated-during-a-new-operation-which-has-exception-in) – Sander De Dycker Oct 16 '18 at 12:28
  • @Dukeling: It's clearly not a single operation when calling custom constructors. It has a lot of code under the hood to make it atomic. – Mooing Duck Oct 16 '18 at 19:04

3 Answers3

26

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.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • 6
    @Galaxy Glad to help. Now. stop using manual memory management in C++ =P Stick with [RAII](https://en.cppreference.com/w/cpp/language/raii) idioms. – WhozCraig Oct 16 '18 at 07:13
5

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.

P.W
  • 26,289
  • 6
  • 39
  • 76
2

There is no need to call delete in case of exception in:

array = new Badclass[10];  // May throw exceptions!

There is no memory leak.

As a reference read about new expression on cppreference:

If initialization terminates by throwing an exception (e.g. from the constructor), if new-expression allocated any storage, it calls the appropriate deallocation function: operator delete for non-array type, operator delete[] for array type.

So it clearly states that delete[] is called automatically, and you don't have to call it.

If part of the objects in were constructed by new[] before the exception was thrown then all object that have been constructed will be destructed before memory is freed. This is just like with object construction that contains an array, and an exception is thrown when constructing some object in the array.

Michael Veksler
  • 8,217
  • 1
  • 20
  • 33