使用迭代器替换地图中常量项的方法
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 提出了相关问题:
- 如果
extract
can remove a node from a container, let you modify its otherwise-const key,然后重新插入它——所有这些都没有任何重新分配——为什么这对于 const mapped_value 是不可能的?
- Const 对象可以被销毁。这也应该适用于容器内的 const 对象——事实上,如果 erase/emplace 变体恰好为节点重用相同的存储(无论是巧合还是通过自定义分配器),它可能会这样做。那么为什么没有办法在不重新分配包含它的地图节点的情况下
replace
具有不同 const mapped_value 的 const mapped_value?
经过一些进一步的实验,我认为以下是当前可用于此场景的最佳方法:
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);
}
我有这样的代码:
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 提出了相关问题:
- 如果
extract
can remove a node from a container, let you modify its otherwise-const key,然后重新插入它——所有这些都没有任何重新分配——为什么这对于 const mapped_value 是不可能的? - Const 对象可以被销毁。这也应该适用于容器内的 const 对象——事实上,如果 erase/emplace 变体恰好为节点重用相同的存储(无论是巧合还是通过自定义分配器),它可能会这样做。那么为什么没有办法在不重新分配包含它的地图节点的情况下
replace
具有不同 const mapped_value 的 const mapped_value?
经过一些进一步的实验,我认为以下是当前可用于此场景的最佳方法:
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);
}