tags:

views:

61

answers:

3

Here is my issue. I'm creating my own GUI Api. All the Widgets are in a container which has add and remove functions. The widgets derive from a base widget class. Here is where I'm unsure. I would ideally like a flow like this: user creates a (desired widget deriving from base class) pointer, the container allocates and manages resources, the user has a pointer to the widget and can make calls to it.

However, polymorphism makes this confusing. How could I get my container to create the right type of new? The issue here is that anyone can create a new widget (like SuperTextBoxWidget) which is why supplying a string and doing a switch would not solve this.

My other quick-fix alternative is to make the user responsible for doing the new, and providing the pointer to the container's add function. But this does not feel idiot proof to me, and it seems odd to have the user do the initial allocation, but then the container manages the rest including erasure.

What would be the best and cleanest way to go about this?

Thanks

just an idea of what I have so far:

class AguiWidgetContainer
{

    std::vector<AguiWidgetBase*> widgets;
    public:
        AguiWidgetContainer(void);
        ~AguiWidgetContainer(void);
        void handleEvent(ALLEGRO_EVENT* event);
        int add(AguiWidgetBase *widget);
        bool remove(int widgetId);
};
+1  A: 

I would suggest borrowing from COM and making your base widget class a pure virtual interface which includes a function to destroy itself. Then implementers of your widget don't even all have to use the same allocator (important if you ever cross DLL boundaries).

EDIT: Example:

class IWidget
{
public:
    virtual Size Measure() = 0;
    virtual void Draw(Point) = 0;
    //and so on

    virtual void Release() = 0;
};

class TextBoxWidget : public IWidget
{
    TextBoxWidget() {}
   ~TextBoxWidget() {}
public:
    // implement IWidget functions, etc, etc

    static TextBoxWidget* Create() { return new TextBoxWidget(); }
    virtual void Release() { delete this; }
};

Now TextBoxWidget can only be created with TextBoxWidget::Create() and released with someTBW->Release(), and always uses new and delete inside the same DLL, guaranteeing that they match.

Ben Voigt
I'm not sure I fully understand the concept. Could you elaborate? Thanks
Milo
Doesn't that still mean the user calls create()?
Milo
Absolutely. But the widget class is in control of both allocation and deallocation, there's no possibility of a mismatch. That's what you were worried about, isn't it, that the user would allocate and the container would deallocate with an incompatible function? This style of object deallocating itself definitely works, it's in use by hundreds of thousands of programs.
Ben Voigt
+1  A: 

I can think of at least two ways to do this.

1. Provide a template version of add:

template<class T>
int add() {
  widgets.push_back(new T);
}

2. Use a factory class:

You can have a base factory class that defines methods to allocate (and possibly also free) widgets. Your users then provide their own subclass of the factory that creates the correct type of widget. For example:

class AguiWidgetFactory {
  AguiWidgetBase *createWidget() = 0;
};

class AguiSuperWidgetFactory : public AguiWidgetFactory {
  AguiWidgetBase *createWidget() {
    return new SuperTextBoxWidget();
  }
};

Your add method then takes a factory object as input and uses it to create a new widget.

casablanca
How does the first method work? I don't see how templates solve the creation issue
Milo
Your users simply specify the class name in the template (there are no other parameters) and the compiler automatically generates code to create the correct object, for eg. you can call `add<MyWidget>()`.
casablanca
How do I then call methods from the base class? ex: widgets[i].clicked();do I have to do(WidgetBase)widgets[i].clicked();
Milo
Not at all. Since all your widgets derive from `WidgetBase`, you can call the methods directly.
casablanca
The template method unfortunately introduces many extra compile-time dependencies, and exposes internal details to all callers, which is probably not a good way to make it extensible by third parties.
Ben Voigt
But what I mean is, templates dot declare all my methods
Milo
@Ben: While I partially agree, I don't see a real problem in doing this. Even the STL is mostly inline code.
casablanca
@Milo: The template `T` is just a placeholder for your widget class. You can only call `add` with a class derived from `WidgetBase`, otherwise it will result in a compile error.
casablanca
A: 

My other quick-fix alternative is to make the user responsible for doing the new, and providing the pointer to the container's add function. But this does not feel idiot proof to me, and it seems odd to have the user do the initial allocation, but then the container manages the rest including erasure.

Why does it feel odd to you?

If a container starts taking responsibility of allocating the objects to be contained, it violats SRP. I would recommend that you design your interfaces to accept pointers to Widget objects that are allocated by users. The container is only responsible for containing/storing/retreiving them.

The user also takes responsibility of deleting the allocated objects.

Most of STL containers work in this way.

Chubsdad
Okay I will do it this way
Milo
Containers not allocating the objects is a consequence of using pointers to enable polymorphism. In general the containers do default-construct, copy-construct, and later delete the elements. But they only ever destroy the ones they created, so no rules are violated (provided the ODR is respected, since templates get inlined you can run into big trouble with calls between DLLs built with different compilers.)
Ben Voigt
@Ben Voigt: `How could I get my container to create the right type of new?` I meant that the container should not provide interfaces such as 'create' wherein it becomes responsible for creating the element to be stored. If copy construction etc happens as part of the container semantics, then that's not a problem. I view it as a prerequisite for being 'containable' and not a core functionality of the container
Chubsdad