2010-08-13 51 views
3

这是一个有点做作,但说我有一个类的接口是这样的:在C++中,如何避免使用泛型类型映射的显式向下转换?

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接受一个模板参数来降低内部类型,但是很明显,模板和纯粹的接口不是jive。我目前的选择是隐藏在调用boost :: polymorphic_downcast的CastResource函数中的投射,但我仍然不满意客户需要投射资源的想法。

例如:

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

所以我想我的问题是:是否有持有指针泛型类型不要求用户明确的向下转换更好的办法?我觉得这是一个模板化的方式,但我没有看到它。另外,我制作了这个界面,以便客户可以在需要时轻松地在测试中嘲笑它。

谢谢。

+0

我会说'dynamic_cast'是这里最好的解决方案。你必须认识到函数必须返回不是最派生的类型(这在技术上是不可能的),而是用户期望的类型。否则'NULL'。这正是'dynamic_cast'所做的。将其包装在模板中不会改变1)它仍然是由用户控制的dynamic_cast,2)它不一定是最派生的类型。 – Fozi 2010-08-13 17:42:00

回答

5

是否有更好的方法来保持泛型类型的指针,而不需要用户明确的向下转换?


无论您使用传统的面向对象,又名运行时多态性。然后你被困在基类接口中,或者你必须作弊和向下转换。或者你使用模板,也叫做编译时多态性。然后,您在编译时绑定到单个资源类型。

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

+0

这个概括起来很简洁。我认为我在一个非常圆的方式中要求的是一个异构对象的容器。 boost :: any可能就足够了。 – lhumongous 2010-08-13 19:45:04

1

如果资源容器的目的是只持有指针,那么你可以使它成为一个模板类

+0

这将如何帮助保持(并通过其动态类型检索)不同的资源类型? – sbi 2010-08-13 18:18:51

+0

我认为一个容器类只适用于单一类型的资源,因为它提到接口仅用于测试目的。 – Rishabh 2010-08-13 18:35:11

3

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

只要在IResource内部实施适当的抽象界面,而根本不担心向下转换呢?如果该接口是在父类中实现的,则只需在IResource上进行适当的虚拟呼叫,而不用担心具体是哪种类型,并进行相应的向下转换。

+0

也许,但我不确定是否将接口添加到IResource是一种方法。 IResource的一部分动机是它持有对不一定共享相同接口的对象的引用。 – lhumongous 2010-08-13 17:51:11

+1

如果他们没有共享相同的界面,你做错了。如果他们不共享相同的接口,请使用模板化类。如果他们共享相同的接口,则使用继承并将该接口放在基础中。 – Puppy 2010-08-13 18:52:30

+0

@DeadMG:在这种情况下,什么才是“正确的方式”呢?这些是我正在存储的异构对象。正如其他地方指出的那样,boost :: any也可以填充这个角色。 – lhumongous 2010-08-13 19:14:05

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

你的第一个例子很整洁,阅读很好。但是,我开始怀疑在这一点上我是否应该真正使用boost :: any。 – lhumongous 2010-08-13 18:13:07

+0

这两个示例都包含一个错误:当'dynamic_cast'失败时,它们将取消引用'NULL'指针。 '' – sbi 2010-08-13 18:17:20

+0

我给了评论“可选抛出异常...”。其实这在第一个例子中是不可选的,我会纠正这一点。其次是好的,指针不被解除引用。 – adf88 2010-08-13 18:43:39

0

如果不同资源类型的数量低,相对固定的,你可以只创建额外的方法到资源的容器,例如IStringResource& GetStringResource(string name)。在内部,您可以使用更具体的资源类型将每种资源类型存储在不同的映射中。这为您提供了有关do​​wncast的完整编译时安全。

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

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

我对我的模板语法有点生疏,但我希望这可以得到重点。

+0

我在我的描述中实际提到了这个实现。问题是你不能在纯粹的界面中定义它。 – lhumongous 2010-08-13 20:16:11