views:

313

answers:

6

I have some (C++) functions each containing several calls creating similar arrays of the same basic type on the heap. At various points in these functions, I may need to throw an exception. Keeping track of which arrays have been deleted is a pain, and quite error prone, so I was thinking about just adding the array pointers to a Set<ArrType*>, of which I can just delete every item when I catch an exception, like this:

try
{
   set<ArrType*> sHeap;
   ArrType* myArr = new ArrType[5];
   sHeap.Add(myArr);
   someExternalRoutine(myArr);
   ...
} 
catch(CString s)
{
   DeleteAllPointersInMyHeap(sHeap);
   throw(s);
}

It feels a bit like adding epicycles, but I can't get around the fact that any one of several external calls may throw an exception, and I need to definitely delete all the pointers allocated up to that point.

Is this just foolishness? Should I just add smaller try-catch blocks around the external calls? I'd still end up with little lists of delete A; delete B; delete D; after each one...

+10  A: 

You should use a RAII techinque. You delegate the destruction to another object you create on the stack.

Then when that object goes out of scope it'll deallocate everything, no matter when it goes out of scope, even with an exception.

Arkaitz Jimenez
+19  A: 

Why not use a smart pointer like boost::shared_array or use a stack-allocated std::vector? For single allocations rather than array allocations, you could use boost::shared_ptr.

These implement the RAII for you. Even if you're re-using a concept like RAII, you're still reinventing the wheel if there's already a concrete implementation out there that satisfies your requirements.

Nick Meyer
+2  A: 

You don't have to rely on garbage collection.

You have std::auto_ptr that provides pointer like syntax and wraps a dynamically allocated object. When destroyed, it automatically destroys the object it points to.

You could implement something similar for arrays.

Cătălin Pitiș
auto_ptr is no use if the pointer is inside a std container, as seems to be the case here
anon
@Neil, it's not clear whether the OP would need to put them in a std container if he weren't using it to track what needed to be deleted. That said, auto_ptr *is* much trickier to use...
Nick Meyer
@Neil: What I was saying that something similar with auto_ptr for arrays can be used (and implemented if not available). So the container knows to transfer the ownership of the contained elements.
Cătălin Pitiș
+8  A: 

Instead of

try
{
   set<ArrType*> sHeap;
   ArrType* myArr = new ArrType[5];
   sHeap.Add(myArr);
   someExternalRoutine(myArr);
   ...
}

You just need:

{
   std::vector <ArrType> myArr(5);
   someExternalRoutine(myArr);
}

with no catch block. All allocation and deallocation (whether exceptions are thrown or not) will be handled for you - this is RAII.

anon
Short and to the point. +1
jalf
The only problem I see is in the case myArr should be taken outside that context. You need to copy the vector.
Cătălin Pitiș
A: 

This is a fine idea, although your implementation is a bit unwieldy. How about:

template <class T>
class Garbage
{
    std::vector<T *> v;

    Garbage(const Garbage &);
    Garbage operator=(const Garbage &);

public:
    Garbage() {}

    ~Garbage()
    {
        for (std::vector<T *>::iterator i = v.begin(); 
             i != v.end(); ++i) delete *i;
    }

    void swap(Garbage<T> &other)
    {
        v.swap(other.v);
    }

    T *remember(T *i)
    {
        v.push_back(i);
        return i;
    }

    void forget(T *i)
    {
        v.erase(std::remove(v.begin(), v.end(), i), v.end());
    }
};

Usage:

Garbage<Thing> g;

Thing *t1 = g.remember(new Thing());

Thing *t2 = g.remember(new Thing());

// decide we want to keep t1:
g.forget(t1);
Daniel Earwicker
anon
This is RAII. Little more than a vector of pointers that deletes whatever the pointers point to, and a helpful erase/remove function.
Daniel Earwicker
And why is this better than a vector of smart pointers? Which the OP doesn't appear to need (except for his GC) incidentally.
anon
Can't put `auto_ptr` in a vector. So would have to be `shared_ptr` or similar, using reference counting; why waste cycles on reference counting if you only have a single owner? (Presumably micro-optimisation is of vital importance to the OP, otherwise why use C++?)
Daniel Earwicker
OK, this is an extended joke, yes?
anon
It's a container of pointers to objects whose lifetime is bounded to the lifetime of the container, without the overhead of reference counting - it's an approximation to a `vector` of `unique_ptr` in C++0x, except with the advantage that it's available for use now. If that's a joke then... boom, boom.
Daniel Earwicker
This wheel has already been invented. boost::ptr_vector
Martin York
That's true. I guess the boost guys must be in on the joke!
Daniel Earwicker
+2  A: 

Looks like you are overthinking it.

Rather than useing try {} catch {} use the RAII.
There are several ways to do this looking through the comments (all seem valid).

Option 1:
If you just need a single fixed(or expanding set of ArrType).
Where the lifespan ends at the end of the function

std::vector<ArrType>

Option 2:
If you need multiple arrays of ArrType Where the lifespan ends at the end of the function

boost::ptr_vector<ArrType>

This also allows you to remove the array from the ptr_vector when the object has a longer lifespan.

Notes on try {} catch {}

  • Catch by ref
    • If you catch by a specific type you are suseptable to the slicing problem as derived types are copy constructed into the variable defined in the catch expression.
  • Prefer to catch by const ref
  • When re-throwing use throw; (without the expression)
    • This will re-throw the original exception rather than copying the new exception into the place where the exception handling mechanism hides the exception during stack unwinding.
Martin York