3

我希望标题不会太混乱。我拥有的是一个类StorageManager,其中包含从Storage. 这是一个例子。

struct Storage {};                         // abstract

class StorageManager
{
private:
    map<string, unique_ptr<Storage>> List; // store all types of storage

public:
    template <typename T>
    void Add(string Name)                  // add new storage with name
    {
        List.insert(make_pair(Name, unique_ptr<Storage>(new T())));
    }

    Storage* Get(string Name)              // get storage by name
    {
        return List[Name].get();
    }
};

SayPosition是一种特殊的存储类型。

struct Position : public Storage
{
    int X;
    int Y;
};

感谢我上一个问题的出色答案,该Add功能已经有效。我要改进的是Get功能。它合理地返回一个Storage*我可以使用的指针,如下所示。

int main()
{
    StorageManager Manager;
    Manager.Add<Position>("pos");    // add a new storage of type position

    auto Strge = Manager.Get("pos"); // get pointer to base class storage
    auto Pstn = (Position*)Strge;    // convert pointer to derived class position

    Pstn->X = 5;
    Pstn->Y = 42;
}

有没有办法通过自动返回指向派生类的指针来摆脱这种指针转换?也许使用模板?

4

4 回答 4

2

Get除了@BigBoss 已经指出的内容之外,我看不到您可以为您的成员函数做什么,但您可以改进您的Add成员以返回使用的存储空间。

template <typename T>
T* Add(string Name)                  // add new storage with name
{
   T* t = new T();
   List.insert(make_pair(Name, unique_ptr<Storage>(t)));
   return t;
}

// create the pointer directly in a unique_ptr
template <typename T>
T* Add(string Name)                  // add new storage with name
{
  std::unique_ptr<T> x{new T{}};
  T* t = x.get();
  List.insert(make_pair(Name, std::move(x)));
  return t;
}

编辑临时阻止我们不得不dynamic_castEDIT2实施 MatthieuM 的建议。

您还可以通过接受要插入的类型的值和默认参数来进一步改进函数,但这可能会产生额外的副本。

于 2012-10-28T15:24:41.907 回答
2

利用:

template< class T >
T* Get(std::string const& name)
{
    auto i = List.find(name);
    return i == List.end() ? nullptr : static_cast<T*>(i->second.get());
}

然后在您的代码中:

Position* p = Manager.Get<Position>("pos");
于 2012-10-28T15:14:24.433 回答
1

当您拥有指向某个类的对象的指针或引用时,您所知道的只是它引用的实际运行时对象要么属于该类,要么属于某个派生类auto 在编译时无法知道对象的运行时类型,因为包含auto变量的代码段可能在一个运行两次的函数中——一次处理一种运行时类型的对象,另一个处理具有不同运行时类型的对象!类型系统无法告诉您在具有多态性的语言中究竟有哪些类型在起作用——它只能提供一些约束。

如果您知道对象的运行时类型是某个特定的派生类(如您的示例中),则可以(并且必须)使用强制转换。(人们认为最好使用 形式的static_cast<Position*>强制转换,因为强制转换是危险的,这使得在代码中搜索强制转换变得更容易。)

但一般来说,经常这样做是设计不佳的标志。声明基类并从中派生其他类类型的目的是使所有这些类型的对象都能以相同的方式处理,而无需转换为特定类型。

  • 如果您希望在编译时始终拥有正确的派生类型而不使用强制转换,那么您别无选择,只能使用该类型的单独集合。在这种情况下,PositionStorage.
  • 如果您可以重新安排事情,以便调用者StorageManager::Get()需要对 a进行的所有Position操作都可以通过调用未指定Position特定信息(例如坐标)的函数来完成,那么您可以将这些函数转换为Storage, 和PositionPosition. _ 例如,您可以创建一个函数Storage::Dump(),将其对象写入stdout. Position::Dump()将输出Xand Y,而Dump()其他可能的派生类的实现将输出不同的信息。
  • 有时,您需要能够使用可能是几个本质上不相关的类型之一的对象。我怀疑这里可能就是这种情况。在这种情况下,boost::variant<>是一个很好的方法。variant这个库提供了一种称为访问者模式的强大机制,它允许您指定对对象可能是的每种类型应采取的操作。
于 2012-10-28T16:21:33.457 回答
1

除了这看起来是个糟糕的想法之外……让我们看看我们能做些什么来改善这种情况。

=> 要求默认构造是个坏主意

template <typename T>
T& add(std::string const& name, std::unique_ptr<T> element) {
    T& t = *element;
    auto result = map.insert(std::make_pair(name, std::move(element)));
    if (result.second == false) {
        // FIXME: somehow add the name here, for easier diagnosis
        throw std::runtime_error("Duplicate element");
    }
    return t;
}

=> 盲目地垂头丧气是个坏主意

template <typename T>
T* get(std::string const& name) const {
    auto it = map.find(name);
    return it != map.end() ? dynamic_cast<T*>(it->second.get()) : nullptr;
}

但坦率地说,这个系统漏洞百出。首先可能是不必要的。我鼓励您查看一般问题并提出更好的设计。

于 2012-10-28T15:47:27.850 回答