【问题标题】:In C++, how can you avoid explicit downcasts with a map of generic types?在 C++ 中,如何避免使用泛型类型映射显式向下转换?
【发布时间】:2010-08-13 17:27:50
【问题描述】:

这有点做作,但假设我有一个这样的类接口:

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 采用模板参数来隐藏这一点,该参数在内部向下转换类型,但显然,模板和纯接口不会出错。我目前的替代方法是将转换隐藏在调用 boost::polymorphic_downcast 的 CastResource 函数中,但我仍然对客户端需要转换资源的想法感到不快。

例如:

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

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

谢谢。

【问题讨论】:

  • 我想说dynamic_cast 是这里最好的解决方案。您必须意识到该函数必须返回的不是派生程度最高的类型(这在技术上是不可能的),而是用户期望的类型。或NULL 否则。这正是dynamic_cast 所做的。将其打包在模板中不会改变以下事实:1)它仍然是由用户控制的 dynamic_cast 以及 2)它不一定是派生度最高的类型。

标签: c++ templates interface


【解决方案1】:

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

没有。
要么您使用的是经典的 OO,也就是 运行时多态性。然后你就被基类接口卡住了,或者你不得不作弊和贬低。或者你使用模板,也就是编译时多态。然后,您在编译时被绑定到单一资源类型。

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

【讨论】:

  • 总结得非常简洁。我认为我以一种非常迂回的方式要求的是一个异构对象的容器。 boost::any 可能就足够了。
【解决方案2】:

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

如果只在IResource 中实现适当的抽象接口而不用担心向下转换呢?如果接口是在父类中实现的,你只需要对IResource进行适当的虚调用,而不用担心它具体是哪种类型并进行相应的向下转换。

【讨论】:

  • 也许,但我不确定将接口添加到 IResource 是否可行。 IResource 的部分动机是它持有对不一定共享相同接口的对象的引用。
  • 如果他们不共享相同的界面,你就做错了。如果它们不共享相同的接口,请使用模板类。如果它们确实共享相同的接口,请使用继承并将该接口放在基础中。
  • @DeadMG:那么在这种情况下,“正确的方法”是什么?这些是我存储的异构对象。正如在其他地方指出的那样, boost::any 也可以担任这个角色。
  • @IHumongous:如果你只是存储一堆不相关的对象,并且它们必须在同一个类中(即非模板化),那么使用 boost::any 或 boost::variant。绝对最好的解决方案就是使用模板,但我可以接受它有限制。
【解决方案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);

【讨论】:

  • 你的第一个例子很简洁,读起来也很好。但是,我开始怀疑此时我是否真的应该只使用 boost::any。
  • 两个示例都包含一个错误:当dynamic_cast 失败时,它们将取消引用NULL 指针。 &lt;kaboom&gt;
  • 我给 cmets “可选地抛出异常 ...”。实际上这在第一个示例中不是可选的,我会更正它。第二个没问题,指针没有解引用。
  • @ad68:你是对的,第二个不是取消引用。我刚刚对那个*ret 进行了讨论。对不起,你的脑残。
【解决方案4】:

如果资源容器的目的是只保存指针,那么你可以把它做成一个模板类

【讨论】:

  • 这将如何帮助保存(并按其动态类型检索)不同的资源类型?
  • 我假设容器类仅适用于单一类型的资源,因为有人提到该接口仅用于测试目的
【解决方案5】:

如果不同资源类型的数量较少且相对固定,您可以为资源容器创建额外的方法,例如IStringResource&amp; 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;
}

我的模板语法有点生疏,但我希望这能说明问题。

【讨论】:

  • 我实际上在我的描述中提到了这个实现。问题是您不能在纯界面中定义它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-25
相关资源
最近更新 更多