在C ++中,如何使用泛型类型的映射避免显式的向下转换?

时间:2010-08-13 17:27:51

标签: c++ templates interface

这有点人为,但我说我有这样的类接口:

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; 
};

我有一个这个类的实现,它包含一个IResource类型的字符串映射。如果我要添加我自己的资源:

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

然后检索资源引用

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

这不会编译因为我需要将IResource转发到CFooResource。我想通过让GetResource获取一个模板参数来隐藏它,这个参数在内部向下转换类型,但很明显,模板和纯接口不会生成。我当前的替代方法是在CastResource函数中隐藏转换,该函数调用boost :: polymorphic_downcast,但我仍然不满意客户端需要转换资源的想法。

例如:

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

所以我想我的问题是:是否有更好的方法来保存指向通用类型的指针,这些指针不需要用户进行明确的向下转换?我觉得有一种模板化的做法,但我没有看到它。此外,我创建了这个界面,以便客户可以在需要时轻松地在测试中模拟它。

感谢。

5 个答案:

答案 0 :(得分:5)

  

有没有更好的方法来保存指向不需要用户显式向下转换的泛型类型的指针?

没有。
你要么使用经典的OO,a.k.a。 运行时多态性 。然后你会遇到基类接口,或者你必须作弊和转发。或者您使用模板,a.k.a。 编译时多态性 。然后,您将在编译时绑定到单个资源类型。

有一些方法可以模糊两者之间的边界(例如boost::any),但基本上这些是你必须决定的两个。

答案 1 :(得分:3)

也许你正试图解决错误的问题。

如何在IResource内实现适当的抽象接口,而不是担心向下转换呢?如果接口是在父类中实现的,则只需在IResource上进行适当的虚拟调用,而不是担心它是哪种类型,并进行相应的向下转发。

答案 2 :(得分:3)

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>();

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);

答案 3 :(得分:1)

如果资源容器的目的是仅保存指针,那么可以将其设为模板类

答案 4 :(得分:0)

如果不同资源类型的数量较少且相对固定,您可以为资源容器创建其他方法,例如IStringResource& GetStringResource(string name)。在内部,您可以将每种资源类型存储在具有更具体资源类型的不同映射中。这为您提供了完整的编译时安全性。

我想不出另一种避免演员阵容的方法。但是,由于您要返回对资源的引用,因此仍然需要处理未找到资源的情况。这必须通过抛出异常来处理,因为你不能返回0.如果你要抛出异常,你可以通过创建模板方法让你的生活更轻松。

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

我的模板语法有点生疏,但我希望这可以解决问题。