views:

142

answers:

5

Hey all,

This is a little contrived, but say I have a class interface like this:

class IResource;
class IResourceContainer
{
 public:
    virtual ~IResourceContainer() {}
    virtual void AddResource(const std::string& rStrName, 
                             std::auto_ptr<IResource> apResource)=0;
    virtual IResource& GetResource(const std::string& rStrName)=0; 
};

and I have an implementation of this class which contains a map of strings to IResource types. If I were to add my own resource like this:

container.AddResource("foo", std:auto_ptr<IResource>( new CFooResource);

and then later retrieve the resource reference

CFooResource& fooResource = container.GetResource(); // error

This wouldn't compile since I would need to downcast the IResource to a CFooResource. I thought about hiding this by making GetResource take a template parameter which downcasts the type internally, but obviously, templates and pure interfaces don't jive. My current alternative is to hide the casting in a CastResource function which calls boost::polymorphic_downcast, but I'm still not thrilled with the idea that a client will need to cast the resource.

For example:

CFooResource& fooResource = CastResource<CFooResource&>(container.GetResource());

So I guess my question is: is there a better way of holding pointers to generic types that don't require explicit downcasts from the user? I feel like there's a templated way of doing this, but I'm not seeing it. Also, I made this interface so that clients could easily mock it out in their tests if need be.

Thanks.

+1  A: 

If the purpose of the resource container is to only hold pointers then you could make it a template class

rtdecl
How would that help to hold (and retrieve by their dynamic type) different resource types?
sbi
I assumed that a container class is for a single type of resource only, since it was mentioned that the interface was only for testing purposes
rtdecl
+3  A: 

Perhaps you're trying to solve the wrong problem.

What about just implementing the appropriate abstract interface within IResource and not worrying about downcasting at all? If the interface is implemented in the parent class, you just have to make the appropriate virtual calls on the IResource rather than worrying about which type it is specifically and doing the corresponding downcast.

Mark B
Perhaps, but I'm not sure if adding the interface to IResource is the way to go. Part of the motivation for IResource is that it holds reference to objects that don't necessarily share the same interface.
lhumongous
If they don't share the same interface, you've done it wrong. If they don't share the same interface, use a templated class. If they do share the same interface, use inheritance and put that interface in the base.
DeadMG
@DeadMG: what would be the "right way" in this case then? These are heterogeneous object that I'm storing. As it was pointed out elsewhere, boost::any could also fill this role.
lhumongous
@IHumongous: If you're just storing a bunch of unrelated objects, and they MUST be in the same class (i.e., nontemplated), then use boost::any or boost::variant. The absolute best solution is just to use a template, but I can accept that it has restrictions.
DeadMG
+4  A: 

is there a better way of holding pointers to generic types that don't require explicit downcasts from the user?

No.
Either you're using classic OO, a.k.a. run-time polymorphism. Then you are stuck with base class interfaces or you have to cheat and down-cast. Or you use templates, a.k.a. compile-time polymorphism. Then you are bound to a single resource type at compile-time.

There are ways to blur the border between the two a little bit (boost::any, for example), but basically these are the two between you have to decide.

sbi
This sums it up pretty succinctly. I think what I was asking for in a very round-about way was a container of heterogeneous objects. boost::any will probably suffice.
lhumongous
+3  A: 
class ResourceWrapper {
private:
    IResource *resource;

public:
    ResourceWrapper() { }
    ResourceWrapper(IResource *resource) : resource(resource) { }
    ResourceWrapper(ResourceWrapper wrapper) : resource(wrapper.resource) { }

    template <class T>
    T &As()
    {
        if (resource == NULL) return NULL;
        T *ret = dynamic_cast<T*>(resource);
        if (ret == NULL) throw Exception("wrong resource type");
        return ret;
    }
};

class IResourceContainer
{
public:
    virtual ~IResourceContainer() {}
    virtual void AddResource(const std::string& rStrName, 
    std::auto_ptr<IResource> apResource)=0;
    virtual ResourceWrapper GetResource(const std::string& rStrName)=0; 
};

CFooResource& fooResource = container.GetResource("name").As<CFooResource>();

or

class ResourceWrapper {
private:
    IResource *resource;

public:
    ResourceWrapper() { }
    ResourceWrapper(IResource *resource) : resource(resource) { }
    ResourceWrapper(ResourceWrapper wrapper) : resource(wrapper.resource) { }

    template <class T>
    void Get(T **ret)
    {
        *ret = dynamic_cast<T*>(resource);
        /* optionally throw exception when dynamic_cast fails */
    }
};

class IResourceContainer
{
public:
    virtual ~IResourceContainer() {}
    virtual void AddResource(const std::string& rStrName, 
    std::auto_ptr<IResource> apResource)=0;
    virtual ResourceWrapper Resource(const std::string& rStrName)=0; 
};

CFooResource *fooResource;
container.Resource("name").Get(&fooResource);
adf88
Your first example is pretty neat and reads well. However, I'm starting to wonder if I should really just use boost::any at this point.
lhumongous
Both examples contain a bug: When the `dynamic_cast` fails, they will dereference a `NULL` pointer. `<kaboom>`
sbi
I gave comments "optionally throw exception ...". Actually this is not optional in the first example, I'll correct that. Second is OK, pointer is not dereferenced.
adf88
@ad68: You're right, the second one isn't a dereferencing. I just pavlov'ed on that `*ret`. Sorry for that brainfart.
sbi
A: 

If the number of different resource types is low and relatively fixed, you could just create additional methods to your resource container, for example IStringResource& GetStringResource(string name). Internally, you can store each resource type in a different map with the more specific type of the resource. This gives you complete compile-time safety concerning downcasts.

I can't think of another way to avoid the casts. However, since you are returning references to your resources, you still have to handle the case that the resource ist not found. This has to be handled by throwing an exception, as you can't return 0. If you're going to throw an exception anyway, you can make your life easier by creating a template method.

T& GetResource<T>(string name) 
{
    IResource* res = ...fetch resource...
    T* tres = dynamic_cast<T*>(res);
    if (tres == 0)
    {
         throw ResourceNotFoundException();
    }
    return *tres;
}

I am a little rusty with my template syntax, but I hope this gets the point across.

TheFogger
I actually mention this implementation in my description. The issue is that you can't define this in a pure interface.
lhumongous