【问题标题】:Modify key of std::map修改 std::map 的键
【发布时间】:2018-08-10 11:03:32
【问题描述】:

有没有办法修改std::map 或的密钥? This example 展示了如何通过重新平衡树来做到这一点。但是,如果我提供一些不需要重新平衡密钥的保证呢?

#include <vector>
#include <iostream>
#include <map>


class Keymap
{
private:    
    int key; // this key will be used for the indexing
    int total;
public:
    Keymap(int key): key(key), total(0)
    {}
    bool operator<(const Keymap& rhs) const{
        return key < rhs.key;
    }
    void inc()
    {
        total++;
    }
};
std::map<Keymap, int> my_index;



int main (){
    std::map<Keymap, int> my_index;
    Keymap k(2);
    my_index.insert(std::make_pair(k, 0));

    auto it = my_index.begin();

    it->first.inc(); // this won't rebalance the tree from my understanding
    return 0;
}

由于it-&gt;first的常量,修改不会编译

有没有办法覆盖这种行为?

【问题讨论】:

  • 你能举一个重要的实际例子吗?
  • 你是绑定到容器类型std::map&lt;Keymap, int&gt;,还是可以改用struct Value { int total; int index; }; std::map&lt;int, Value&gt;之类的东西?
  • @bobah 在某些情况下,您可能希望在对象之间进行映射,其中键比文字映射键或具有类似属性的集合具有更多功能。更准确地说,它肯定可以有其他成员不影响其排序顺序并且应该可以修改。比如说,你得到一个std::set&lt;Cars, SortByLicensePlate&gt; carsToInspect;,你想在处理完检查日期后更改检查日期。您可以(并且可能应该)在此处使用std::vector,但您明白了。
  • @MaxLanghof - 所有这些用例都是设计缺陷。在您的示例中,它应该是 std::map&lt;CarLicensePlate, Car&gt; 或类似于 boost::multi_index 的东西,作为索引容器,它始终支持数据修改。
  • @bobah 你有一个有效的观点,这可以被视为设计缺陷。但是,您提出的建议要么必须将 CarLicensePlate 移出 Car 类,要么具有内存效率低下的重复字段。所以这一切都归结为关键与对象的关系。但我明白你的意思,很难用这个来说明问题。就我而言,我想避免重构以存储一些元数据的成本。

标签: c++ stl


【解决方案1】:

您可以使 inc const 和 total 可变

class Keymap
{
private:    
    int key; // this key will be used for the indexing
    mutable int total;
public:
    Keymap(int key): key(key), total(0)
    {}
    bool operator<(const Keymap& rhs) const{
        return key < rhs.key;
    }
    void inc() const
    {
        total++;
    }
};

但你确实需要问自己为什么要这样做,mutable 用得不多。

你是对的,不会发生再平衡。

【讨论】:

  • “没怎么使用”不是问自己为什么要这样做的理由。 mutable 有它的用途和必要性。当前案例实际上是 mutable 的一个有效用例,尽管有人可能会争辩说它允许进行太多修改。
  • 您如何确定这是mutable 的有效用例?我们没有看到total 是如何使用的,以及它是否有任何外部可观察到的效果(它目前没有,但它也没有任何用途,所以这个例子显然是不完整的)。你需要提出一个很好的案例来使用mutable,而不仅仅是“它在这里有帮助”......
【解决方案2】:

如果您无法更改设计并引入代理只读键,您最好的选择是使用Boost.MultiIndex 容器(我不知道合理的替代方案)。它专为此目的而设计,并具有对更新索引对象(包括事务变体)的一致内置支持。文档和代码示例是here

一般而言,诸如将业务实体存储在自键集中、具有用于其他目的(计数器等)的可变键等模式往往会对代码的可维护性、性能和可扩展性产生影响。

【讨论】:

    【解决方案3】:

    您可以将您的密钥包装到一个允许修改 const 对象的类中。一类是std::unique_ptr

    using KeymapPtr = std::unique_ptr<Keymap>;
    
    struct PtrComp
    {
        template<class T>
        bool operator()(const std::unique_ptr<T>& lhs, const std::unique_ptr<T>& rhs) const
        {
            return *lhs < *rhs;
        }
    };
    
    template<class V>
    using PtrMap = std::map<KeymapPtr, V, PtrComp>;
    
    int main (){
        PtrMap<int> my_index;
        KeymapPtr k = std::make_unique<Keymap>(2);
        my_index.emplace(std::move(k), 0);
    
        auto it = my_index.begin();
    
        it->first->inc(); // this won't rebalance the tree from my understanding
        return 0;
    }
    

    Demo

    请注意,我们必须提供一个自定义比较器对象,因为我们(可能)希望按键值排序,而不是指针值。

    需要明确的是,这不是 unique_ptr 的含义,而智能指针的 const 语义(遵循常规指针的语义)从这个角度来看有点倒退(为什么我可以得到一个非const 来自const 对象的引用?linter 可能会抱怨这种用法...),但它在这里起到了作用。当然,这同样适用于裸指针(T* const 可以更改 T 值但不能更改指针位置,而 const T* 可以更改其位置但不能更改 T),但这模仿原始代码的所有权/生命周期模型。

    不用说,这打开了打破地图不变量(通过键打破排序)的大门,所以在使用它之前要三思而后行。但与@987654332 直接@你的密钥不同,它是免费的。

    【讨论】:

      【解决方案4】:

      std::map 和其他标准关联容器不提供在不删除和添加元素的情况下执行此操作的方法,这可能会导致树重新平衡的副作用。您可以通过各种方式绕过映射键常量(例如,使用 mutable 成员),但是确保您实际上不会破坏键顺序完全取决于您。

      如果您需要这种效率但更安全一些,您可以考虑将容器改为boost::multi_index_container

      std::map&lt;K,V&gt; 类似于:

      namespace BMI = boost::multi_index;
      using map_value_type = std::pair<K, V>;
      using map_type = BMI::multi_index_container<
          map_value_type,
          BMI::indexed_by<BMI::ordered_unique<
              BMI::member<map_value_type, &map_value_type::first>
      >>>;
      

      除了在multi_index_container 中,整个元素总是const。如果您希望能够直接修改second 成员,this boost page 中描述了一种方法。

      multi_index_container 提供了两个标准关联容器没有的成员,replace and modify。这两个都将检查修改后的元素是否处于相同的排序顺序。如果是,则不进行重新平衡。

      auto it = my_index.begin();
      auto pair = *it;
      pair.first.inc();
      my_index.replace(it, pair);
      
      // OR
      
      auto it = my_index.begin();
      my_index.modify(it, [](auto& pair) { pair.first.inc(); });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-23
        • 2011-10-26
        相关资源
        最近更新 更多