Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic memory and constructor exceptions

Early today I discovered function try-catch blocks (from here in fact) and then went on a bit of a research spree - apparently they're main use is it catch exceptions throw in by a constructor initialiser list.

Anyway, this sent me thinking about failing constructors and I've got to a stage where I just need a little clarification. This is all just me trying to learn more about the language, so I don't have a practical example, but here goes...


Given this example code:

class A
{
private:
    B b
    C *c;    //classes B, C & D omitted for brevity as not really relevant
    D d;
public
    A(int x, int y, int z)
};

A::A(int x, int y, int z)
try
    : b( x )
    , c( new C(y) )
    , d( z )
{
    //omitted
}
catch(...)
{
    //omitted
}

What happens in these cases:

  1. The initialisation of b throws an exception.
  2. The initialisation of c throws an exception.
  3. The initialisation of d throws an exception.

Specifically, I want to know at least:

  • what will/may cause a memory leak from new C(y). I'm thinking only 3? (see here)
  • could you just delete b in the catch? Is dangerous in cases 1 and 2?

Obviously, I guess the safest thing to do is to make c a smart pointer. But disregarding that option for the moment, what's the best course of action?

Is it safe to set c to NULL in the initialiser, and then place the call to new in the constructor body?

That would then mean a delete c must be placed in the catch in case something else throws in the constructor body? Are there safety issues doing that (ie, if it's the c = new C(y); itself that throws)?

like image 649
DMA57361 Avatar asked Jul 05 '10 16:07

DMA57361


1 Answers

Try/catch function blocks are frowned upon, in the same way as goto --there might be some corner case where they make sense, but they are better avoided: when an object fails to be constructed, best thing you can do is fail and fail fast.

On you specific questions, when an exception is thrown in a constructor, all fully constructed subobjects will be destroyed. That means that in the case of b it will be destroyed, in the case of c, it being a raw pointer, nothing is done. The simplest solution is changing c to be a smart pointer that handles the allocated memory. That way, if d throws, the smart pointer will be destroyed and the object released. This is not related to the try/catch block, but rather to how constructors work.

It is also, in general, unsafe to delete the pointer from the catch block, as there is no guarantee of the actual value of the pointer before the initialization is performed. That is, if b or c throw, it might be the case that c is not 0, but is not a valid pointer either, and deleting it would be undefined behavior. As always there are cases, as if you have a guarantee that neither b nor c will throw, and assuming that a bad_alloc is not something you usually recover from, then it might be safe.

Note that if you need to keep a pointer with a raw pointer for some specific reason, it is better to initialize it to 0 in the initialization list, and then create the object inside the construction block to avoid this problem. Remember also that holding more than one (raw) resource directly in a class makes it really hard to ensure that no resource is leaked -- if first resource is created and assigned, and the second one fails, the destructor will not be called and the resource will leak. Again, if you can use a smart pointer you will have one less problem to deal with.

like image 91
David Rodríguez - dribeas Avatar answered Oct 04 '22 20:10

David Rodríguez - dribeas