views:

71

answers:

4

Consider the following class structure:

class Filter
{
    virtual void filter() = 0;
    virtual ~Filter() { }
};

class FilterChain : public Filter
{
    FilterChain(collection<Filter*> filters)
    {
         // copies "filters" to some internal list
         // (the pointers are copied, not the filters themselves)
    }

    ~FilterChain()
    {
         // What do I do here?
    }

    void filter()
    {
         // execute filters in sequence
    }
};

I'm exposing the class in a library, so I don't have control over how it will be used.

I'm currently having some design issues regarding ownership of the Filter objects FilterChain is holding pointers to. More specifically, here are two possible usage scenarios for FilterChain:

  • Scenario A: some of the functions in my library are constructing a (possibly complex) filter chain, allocating memory as necessary, and returning a newly-allocated FilterChain object. For example, one of these functions constructs a filter chain from a file, which can describe arbitrarily-complex filters (including filter chains of filter chains, etc.). The user of the function is responsible for object destruction once the job is done.
  • Scenario B: the user has access to a bunch of Filter objects, and wants to combine them in filter chains in a specific manner. The user constructs FilterChain objects for its own use, then destroy them when he's done with them. The Filter objects must not be destroyed when a FilterChain referencing them is destroyed.

Now, the two simplest ways to manage ownership in the FilterChain object are:

  • FilterChain own the Filter objects. This means the objects referenced by FilterChain are destroyed in FilterChain's destructor. Which is incompatible with scenario B.
  • FilterChain does not own the Filter objects. This means FilterChain's destructor does nothing. Now there is a problem with scenario A, because the user would have to know the internal structure of all the Filter objects involved in order to destroy them all without missing one, as the parent FilterChain does not do it itself. That's just bad design, and asking for memory leaks.

Consequently, I need something more complicated. My first guess is to design a smart pointer with a settable boolean flag indicating whether or not the smart pointer owns the object. Then instead of taking a collection of pointers to Filter objects, FilterChain would take a collection of smart pointers to Filter objects. When FilterChain's destructor is called, it would destroy the smart pointers. The destructor of the smart pointer itself would then destroy the object being pointed to (a Filter object) if and only if the boolean flag indicating ownership is set.

I get the feeling that this problem is commonplace in C++, but my web searches for popular solutions or clever design patterns were not very successful. Indeed, auto_ptr doesn't really help here and shared_ptr seems overkill. So, is my solution a good idea or not?

A: 

FilterChain should have a separate DeleteAll() method, which iterators over the collection and deletes the filters. It is called in Scenario A and is not called in Scenario B. This does require some intelligence on the part of the users of FilterChain, but no more then remembering to delete and object that they new'd. (They should be able to handle that, or they deserve a memeory leak)

James Curran
They shouldn't *have* to remember to delete anything, anywhere. Use containers, smart pointers, etc. Be exception-safe, be clean, never explicitly manage things outside a utility class.
GMan
+2  A: 

Are filters so big that you can't simply make a deep copy of each one when you create the FilterChain? If you were able to do that, then all your problems disappear: The FilterChain always cleans up after itself.

If that's not an option due to memory concerns, then using shared_ptr seems to make the most sense. The caller will have to be responsible for keeping a shared_ptr for each object it cares about and then the FilterChain will know whether to delete particular filters or not when it is deleted.

EDIT: As Neil noted Filter needs a virtual destructor.

Mark B
Filter objects are non-copyable, not because of memory concerns, but because it would cause issues related to duplication of state information (also, it would be semantically dubious).
e-t172
@e-t172 Then what about having the filter state be stored in a `shared_ptr` so you can copy `Filter` s?
Mark B
What's the difference with using shared_ptr in FilterChain?
e-t172
@e-t172 It just changes where the reference counting happens. If you allow `Filter` objects to have a reference-counted state, then you can copy them at will, completely removing the need for clients to create a `shared_ptr` to a Filter at all: They can just pass in the collection and understand that they still own all the filters in the collection AND that the `Filtercontainer` will properly clean up all its filters when its done.
Mark B
A: 

I would go with FilterChain not owning the Filter objects. Then, in your library, when you need to load a FilterChain from the file you would have another Loader object that is responsible for the lifetime of the Filter objects. So, the FilterChain would work consistently for Chains loaded by the library and user created Chains.

Chad Simpkins
That would mean duplicating the possibly complex filter structure into the Loader object, just for managing ownership. That's excessively complex compared to the other solutions.
e-t172
In fact what you're proposing is simply the second "solution" in my original question, except that you're moving the logic of managing filter objects from outside the library to the inside. Indeed the code is simpler for the user of the library, but just because the excessive complexity is hidden inside the library does not mean it's good design (it's necessary but not sufficient).
e-t172
I can't see why you would need to duplicate the filter structure in the loader. The loader would only need to manage the lifetime of filters. The user of the library would still be required to manage the lifetime of their own filters. The filters should (de)serialize themselves. Whenever I see code like this it is because not enough thought was put into the design of the Filter class and now you are paying the price.
Chad Simpkins
"I can't see why you would need to duplicate the filter structure in the loader. The loader would only need to manage the lifetime of filters." I agree. Thing is, a filter could be a simple filter, or a filter chain, or a chain of chains, etc. and we just got back to the original problem.
e-t172
+2  A: 

Smart pointers here are not overkill: obviously you have a design problem that one way or another needs careful consideration of object lifetimes and ownership. This would be especially true if you want the ability to re-patch filters in the filter graph at runtime, or the ability to create compound FilterChain objects.

Using shared_ptr will remove most of those issues in one swoop and make your design a lot simpler. The only potential gotcha I think here is if your filter happens to contain cycles. I can see that could happen if you have some kind of feedback loop. In that instance I would suggest having all Filter objects owned by a single class, and then the FilterChain would store weak pointers to the Filter objects.

I would wager that the execution time of the filter stages would be far in excess of the extra overhead of dereferencing a smart pointer. shared_ptr is designed to be pretty lightweight.

the_mandrill
Seems like everybody here thinks it's The Right Thing To Do. Alright then let's go for shared_ptr.
e-t172
I used to have a bit of a mental block when it came to smart pointers, but having started to use shared_ptr much more recently I've found it makes life *so* much easier.
the_mandrill