【问题标题】:Automatic Return Type for Pointers in C++C++ 中指针的自动返回类型
【发布时间】:2012-10-18 01:51:06
【问题描述】:

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

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

【问题讨论】:

  • 如果Storage是基类,那么请给它一个虚拟析构函数。
  • 感谢您的建议。我将在我的真实代码中使用它。
  • 注意:使用List[Name]的想法会造成尴尬。对于mapunordered_mapoperator [] 将创建值(调用默认构造函数),如果它不存在的话。因此,如果您尝试使用不存在的名称来Get,则会创建指针,从而导致元素“泄漏”。
  • @MatthieuM。好在BigBoss给我提供了更好的解决方案。

标签: c++ class templates pointers return


【解决方案1】:

使用:

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

【讨论】:

  • @Xirdus 我不知道如何改进。您使用类型擦除并且仍然想要静态类型?你需要说出来吗?
  • 这里最好使用dynamic_cast 而不是static_cast。使用static_cast 进行向下转换是在玩火——而且行为未定义。
  • 这样更好,至少一点点。但当然最好完全不必指定类型(在 main 函数中)。
  • @sharethis:这就是你尝试重新发明类型系统时得到的结果......你被告知这会发生the last time you asked :)
  • @sharethis 按照标准C++,在dynamic_cast&lt;T&gt;(v)v shall be a pointer to or an lvalue of a polymorphic type。在你的情况下,如果你的基类不是polymorphic 类型(没有virtual 函数),最好的情况是dynamic_caststatic_cast 一样工作
【解决方案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;
}

EDIT 临时阻止我们必须dynamic_castEDIT2 实施 MatthieuM 的建议。

您还可以通过接受 要插入的类型,带有默认参数,但这可能会导致 附加副本。

【讨论】:

  • 不错的改进。我会用那个。
  • 一般来说,我建议不要使用裸指针。在这种情况下,没有泄漏的风险,但最好立即构建unique_ptr
  • @MatthieuM。你能说明这个想法吗?
  • @sharethis 我添加了类似的内容。
【解决方案3】:

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

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

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

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

【讨论】:

  • +1,如果可以的话,还有更多。总而言之,这只是个坏主意。
  • 也许我会明白为什么有时这是一个可怕的想法。但现在我将采用这种方法,这正是我想要的。
【解决方案4】:

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

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

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

  • 如果您希望在编译时始终拥有正确的派生类型而不使用强制类型转换,那么您别无选择,只能使用该类型的单独集合。在这种情况下,从Storage 派生Position 可能毫无意义。
  • 如果您可以重新安排事情,以便 StorageManager::Get() 的调用者需要与 Position 做的所有事情都可以通过调用未指定 Position 特定信息(例如坐标)的函数来完成,您可以在Storage 中将这些函数变成虚函数,并在Position 中实现它们的Position 特定版本。例如,您可以创建一个函数Storage::Dump(),将其对象写入stdoutPosition::Dump() 会输出XY,而Dump() 对其他可能的派生类的实现会输出不同的信息。
  • 有时您需要能够处理可能是几个本质上不相关的类型之一的对象。我怀疑这里可能就是这种情况。在这种情况下,boost::variant&lt;&gt; 是一个不错的选择。该库提供了一种称为访问者模式的强大机制,它允许您指定对variant 对象可能是的每种类型应采取的操作。

【讨论】:

  • 我同意。一般来说,我更愿意在代码中看到演员表,而不是将它们隐藏在某种机制后面。最好根本不必使用演员表。有时很难避免使用强制转换,因为存在有效的反射用例。即使使用是合理的,也应该公开这些演员表。
  • 您的建议看似合理,但我决定使用该设计有充分的理由。您对改进代码的每一个建议都会完全消除这些好处。不过感谢您的详细回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-04
相关资源
最近更新 更多