【问题标题】:Most appropriate associative STL container when key is part of object [C++]当键是对象的一部分时最合适的关联 STL 容器 [C++]
【发布时间】:2011-10-23 20:38:29
【问题描述】:

我有这样的课:

struct Thing
{
    unsigned index;
    // more data members
};

我正在使用std::map<Thing> 来包含我的Things。调用代码如下所示:

Thing myThing(/*...*/);
std::map<Thing> things;
things[myThing.index] = myThing;
// ...
Thing &thing3 = things[3];

我想知道是否有一种方法可以直接使用Thing::index,而无需将其隐式复制到pair::first

我想我需要提供某种Thing 比较运算符,但这没关系。 std::set 可能有效,但我需要一个完整的 Thing 对象作为键:

std::set<Thing> things;
Thing &thing3 = *things.find(Thing(3));

除了将 Thing 重构为 std::pair 之外,我还能用 STL 做得更好吗?

【问题讨论】:

  • 这是个好问题;但如果你的密钥只是一个整数,那就不值得了。
  • @K-ballo - 你可能是对的 - 我可以通过在 map 插入周围编写模板包装器来改进调用代码,该插入器承认并抽象出对 Thing::index 的引用。跨度>

标签: c++ stl map set containers


【解决方案1】:

我不明白为什么

inline bool operator<(const Thing& a,const Thing& b) {
  return (a.index<b.index);
}

std::set<Thing> things;  // Uses comparison operator above

Thing &thing3 = *things.find(Thing(3));

并没有完全按照您的意愿行事。没有索引字段的复制/复制,并且键比较与map方法一样高效;有什么不喜欢的?

根据以下 cmets 更新

如果 Thing 如此重量级以至于您不想复制它,那么您最终可能会得到更像这样的代码:

inline bool operator<(const shared_ptr<Thing>& a,const shared_ptr<Thing>& b) {
  return (a->index < b->index);
}

std::set<shared_ptr<Thing>> things;  // Uses comparison operator above

shared_ptr<Thing> key3(new Thing(3));   

Thing &thing3 = *things.find(key3);

虽然恕我直言,覆盖指针值比较是相当邪恶的,但最好将显式“比较”参数的更详细的路由设置为模板参数。

要记住的一件事(根据我自己对重量级对象的大优先级队列的经验):基于std::pair&lt;trivial_key,shared_ptr&lt;heavyweight_object&gt;&gt; 的容器可能比std::pair&lt;trivial_key,heavyweight_object&gt; 具有显着优势,因为前者的遍历仅触及与后者相比,密钥(例如find)的缓存效率可能更高,后者还将获取大量大部分不需要/不相关的heavyweight_object字节到缓存中(当然取决于细节和数量,但实际上这种效果很容易完全淹没复制密钥的相对较小的成本)。

【讨论】:

  • 因为Thing 可能有其他非默认可构造成员,或者默认构造繁重的成员(可能正在做资源获取)。
  • 如果 Things 是重量级的,您可能应该查看地图或 shared_ptr 集,或 boost“指针容器”。
  • shared_ptr 无法解决将键作为复杂对象值的一部分的实际问题。
  • 这意味着整个对象将是不可变的。
  • @timday K-Ballo 是对的 - 事情是重量级的,如果我们能提供帮助,我们不想要临时的 Thing(3)。 @UncleBens 你能解释一下你的评论吗?
【解决方案2】:

使用私有继承或组合包装映射,重新导出访问器并根据所需功能实现插入。我建议在 key getter 上对你的新类型进行 patametrize - 一个返回给定存储类型对象的键的函子。

【讨论】:

  • struct Thing : private std::map { /*...*/ }; 是个坏主意,因为map 的析构函数可能不是虚拟的。但除此之外是个好主意。
  • 将继承设为私有的唯一原因是确保结果类型的析构函数是虚拟的。如果您打算将虚拟析构函数排除在外,则使用公共继承会更容易。
  • std::map 的析构函数不是具有私有继承的虚拟函数,这没关系。如果你要说delete BasePtr,你只需要一个虚拟析构函数,其中BasePtr实际上是一个指向Derived的指针。因为您使用的是私有继承,所以没有人能够获得实际上指向 Thingstd::map *
  • @DavidStone:有可能。类方法及其朋友可以执行这种转换。
【解决方案3】:

我记得boost::flyweight 支持key extractors 以避免在可以从对象中轻松获得密钥时为密钥保留额外的存储空间。

我不建议在 mapunordered_map 足够的所有情况下使用享元,我很难相信这些类不支持类似的密钥提取模式,尽管我找不到任何在文档或谷歌中提到。无论哪种情况,这都可能对您有所帮助

【讨论】:

    【解决方案4】:

    使用对象的内存地址作为映射中的键怎么样?

    std::map< void*, std::shared_ptr<MyObject> > myMap;
    myMap.insert( std::pair<void*, std::shared_ptr<MyObject> >(object.get(), object) );
    

    因此您不需要将密钥存储在对象中,我认为这是一个坏主意,因为密钥与对象状态没有任何关系。

    【讨论】:

    • 那么你如何找到Thingindex == 3
    • 您永远不会分配索引,只需将指针的值保留为索引。也许我的一对中的 void* 是错误的,更好的解决方案必须是 MyObject* 作为关键。无论如何,例如,如果您想从列表中删除对象,您将传递对象的指针。
    • 如果我没有指向地图外对象的指针? - 另外,如果 index 成员可以从结构中删除,则此问题将不存在。
    • 当然在这种情况下必须删除索引成员。我看不出将键保留为对象成员的原因 现实世界中没有任何对象包含此对象的索引,而且如果您将此对象放在第二个列表中,您是否会创建一个新索引作为这个对象?内存地址是一个物理索引,它将保证您永远不会从列表中删除错误的对象。
    猜你喜欢
    • 2010-11-12
    • 2011-09-12
    • 2021-09-22
    • 2020-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多