C ++中指针的自动返回类型

时间:2012-10-28 15:02:32

标签: c++ class templates pointers return

我希望标题不会太混乱。我所拥有的是一个类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();
    }
};

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

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

感谢我last question上的优秀答案,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 个答案:

答案 0 :(得分: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");

答案 1 :(得分:2)

除了@BigBoss已经指出的内容之外,我没有看到您可以为Get成员函数做些什么,但是您可以改进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_cast EDIT2 实施MatthieuM的建议。

您还可以通过接受值来进一步改进功能 要插入的类型,使用默认参数,但可能会产生 另外的拷贝。

答案 2 :(得分:1)

除了这看起来像一个可怕的想法之外......让我们看看我们可以做些什么来改善这种情况。

=&GT;要求默认构造

是个坏主意
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;
}

=&GT;盲目低调是个坏主意

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;
}
但坦率地说,这个系统充满了漏洞。首先可能是不必要的。我鼓励您回顾一般问题,提出更好的设计。

答案 3 :(得分:1)

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

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

但总的来说,这样做很多都是设计不佳的标志。声明基类并从中派生其他类类型的目的是使这些类型的 all 对象以相同的方式处理,而不会转换为特定类型。

  • 如果您希望在编译时始终拥有正确的派生类型而不使用强制类型转换,那么您别无选择,只能使用该类型的单独集合。在这种情况下,Position可能无法获得Storage
  • 如果你可以重新安排事情,那么StorageManager::Get()的调用者需要对Position执行的所有事情都可以通过调用未指定Position的函数来完成 - 具体信息(例如坐标),您可以将这些功能转换为Storage中的虚拟功能,并在Position中实现Position - 特定版本的功能。例如,您可以创建一个函数Storage::Dump(),将其对象写入stdoutPosition::Dump()会输出XY,而其他可以设想的派生类的Dump()实现会输出不同的信息。
  • 有时您需要能够使用可能是几种基本上不相关的类型之一的对象。我怀疑这可能就是这种情况。在这种情况下,boost::variant<>是一个很好的方法。该库提供了一种称为访问者模式的强大机制,允许您指定应为variant对象可能采用的每种类型采取的操作。