使用迭代器替换地图中常量项的方法

Way to replace constant item in a map using iterator

我有这样的代码:

std::map<int, const Data> all_data;
// ...
bool OldDataIsBetter(Data const& oldData, Data const& newData) {...}
// ...
void AddData(int key, Data&& newData)
{
    auto [it, ok] = all_data.try_emplace(key, std::move(newData));
    if (!ok)
    {
        if (OldDataIsBetter(*it, newData)) return;
        it->second = std::move(newData);
    }
}

这不会编译,因为 it->second 引用了一个 const Data,所以它的赋值运算符不能被调用。如果 const 被删除,它工作正常。

以上的目的是insert_or_assign,除了如果一个项目已经存在然后我需要比较旧的和新的项目,看看哪个是"better"。

const 声明地图元素类型的目的是数据一旦在地图中就应该是不可变的——项目作为一个整体可以被替换,但不能被零碎地修改。

我可以 "fix" 以上 通过重新分配给容器:

all_data[key] = std::move(newData);

(结果发现const也有同样的问题。)

或者通过擦除并重试该位置:

all_data.erase(it);
all_data.emplace(key, std::move(newData)); // should never fail

但这两个看起来都不优雅,因为我已经有一个迭代器指向应该被替换的项目,上面两个都忘记了,再去搜索。

是否有更好的方法来完成此替换?


聊天线程中的 TLDR 提出了相关问题:

经过一些进一步的实验,我认为以下是当前可用于此场景的最佳方法:

it = all_data.erase(it);
all_data.emplace_hint(it, key, std::move(newData)); // should never fail

这对我来说仍然不太理想,因为它仍然会重新分配地图节点。这不是世界末日,但如果可能的话,我想找到一种方法来避免它。

const 表示不可变,不是部分可变的。如果您在对象声明中使用 const(这就是您在该模板参数中粘贴 const 时所做的事情),C++ 相信您 是认真的 。它会让你坚持下去。

所以 const Data 是不可能的。

根据你的问题,我推测 Data 有一些函数可以设置它的某些部分的状态,你不希望用户调用这些函数。但是您希望用户能够覆盖该值。

这样做的方法是提供一个包装 Data 实例的对象类型,允许从 Data 对象进行赋值。此包装器将提供 Data 提供的 const 访问器的版本。但是该类型不提供 Data.

的非 const 修饰符

在您的情况下,您真正​​想要做的是有条件地覆盖数据元素。

这强烈支持可变数据元素。

在一般情况下,容器必须具有不可变元素,因此您可以提供一个提供 const 访问的包装器,除非明确要求可变访问:

#include <map>
#include <functional>

struct Data {};

struct MyDataMap
{
    using store_type = std::map<int, Data>;
    using iterator = store_type::const_iterator;

    template<class Condition>
    std::pair<iterator, bool> 
    replace_if(int key, Data&& value, Condition cond)
    {
        auto [it, inserted_or_replaced] = store_.try_emplace(key, std::move(value));
        if (!inserted_or_replaced && cond(it->second, value))
        {
            it->second = std::move(value);
            inserted_or_replaced = true;
        }
        return std::make_pair(it, inserted_or_replaced);
    }

    // other accessors as necessary

    iterator begin() const { return store_.cbegin(); }
    iterator end() const { return store_.cend(); }

private:

    store_type store_;
};

// ...
bool OldDataIsBetter(Data const& oldData, Data const& newData);
// ...

void test(MyDataMap& m, int k, Data newd)
{
    auto oldIsWorse = std::not_fn(OldDataIsBetter);
    auto [it, replaced] = m.replace_if(k, std::move(newd), oldIsWorse);    
}