修改"std::set"中用户定义类型的值



考虑这样一种情况:我有一个用户定义的类型,比如一个返回唯一std::stringid()成员函数。

我想要一个包含这些对象的容器,其中id()唯一地标识元素,但我想"使用"这些对象来做其他可能修改其成员的事情。

我目前正在通过调用std::set::emplace并捕获返回的迭代器bool对来构建对象。

但是我不允许修改它的值,因为迭代器是const。

有什么好方法可以做我想做的事吗?我能想到的只有两个:

  1. unique_ptr存储到set中的对象,这样指针值是区分它的原因,而不是名称,并且可以修改指向的对象
  2. 使用id()作为密钥存储map,但这意味着我复制了密钥

如果他们有合适的容器来解决我的问题,我很乐意使用采用良好的现代库,比如boost。

有什么好方法可以做我想做的事情吗

不是。std::set的粒度在对象级别。无法表示对象的一部分对键有贡献。

有些人建议将所有非关键成员声明为mutable。这是错误的,因为mutable用于对象的公共接口(例如互斥体)中隐藏的东西。

"官方"的方法是将对象从集合中取出,对其进行修改,然后将其放回集合中。C++17具有set::extract,这有助于稍微提高此任务的性能(当然,如果从不修改键,这仍然是低效的,因为树仍然需要检查/重新平衡)。

我想"使用"对象来做其他可能修改其成员的事情

如果您绝对确定从未修改对象键,请丢弃constness。从法律的角度来看,去掉非const生成的对象的常量是可以的。为了额外的安全,你可以把钥匙包在另一个const成员中:

struct Element {
const Key key;
Value value;
};

如果您有一个包含多个集的数据多维数据集,每个集都在键上使用自己的"视图",这将没有帮助。

1.将unique_ptr存储到set中的对象

由于额外的间接性,这将是一个令人讨厌的现象。由于元素在堆上,你会有一个额外的缓存未命中。如果你不小心修改了键,最终会得到UB。

2.使用id()作为密钥存储map

是的,这种方法可以有不同的变体,但您仍然必须确保永远不要修改密钥。

例如,您可以存储指向数据的键+指针。这种方法通常与具有线性探测的dense_hash_set相结合,以获得最佳性能。由于在找到元素后只访问一次值,因此它位于其他位置并不重要。

我建议使用Boost.MultiIndex作为std::set的替代品,因为它添加了允许修改元素的modify方法,检查容器内的位置是否已更改:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
struct S { /* ... */ };
boost::multi_index_container<S> t; // default configuration emulates std::set<S>
auto [it, inserted] = t.emplace(...);
t.modify(it, [&](S& s) {
// modify s here
// if the key is unchanged, s does not move
// the iterator `it` remains valid regardless
});

示例。

在检查密钥是否确实不变时会有一点开销,但与程序的其他部分相比,这应该是最小的,并且应该很好地进行优化和预测。

std::set保持其元素的排序,元素排序的与元素本身相对应。因此,std::set中的元素是符合const条件的,以防止用户修改元素(即密钥),从而破坏std::set顺序。

传统上,如果要修改std::set的元素,则必须首先从std::set中删除要修改的元素对象,对其进行修改,然后再次将其插入到std::set中。问题在于,这导致分配std::set内部节点。

由于std::set::extract(),由于C++17,您可以在不分配std::set内部节点的情况下将元素移除并重新插入到std::set中。此成员函数返回与请求的元素相对应的节点句柄。通过此返回的节点修改元素后,可以重新插入具有相应insert()重载的节点。在重用已分配的节点时,不会进行节点分配。

无论是否发生分配,这些方法的缺点是,将元素重新插入std::set需要集合大小的对数时间(除非您可以利用insert()的提示)。

去掉常量修改std::set元素

只要std::set的比较函数不考虑您更改的数据成员,您仍然可以从std::set的元素中强制转换const并修改其数据成员。也就是说,如果只修改属于std::set的元素的数据成员,而该元素的比较函数没有考虑该元素,则顺序不会中断。

相关内容

最新更新