有没有办法修改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->first
的恒定性,修改无法编译
有什么方法可以替代此行为?
答案 0 :(得分:4)
您可以使inc
常量和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
并没有太多使用。
您是正确的,不会进行任何重新平衡。
答案 1 :(得分:2)
如果您不能更改设计并引入代理只读键,则最好的选择是使用Boost.MultiIndex容器(我不知道合理的选择)。它是专门为此目的设计的,并且具有一致的内置支持来更新索引对象(包括事务变体)。文档和代码示例为here。
通常,诸如将业务实体存储在自密钥集中,具有用于其他目的的可变密钥(计数器和诸如此类)之类的模式往往会影响代码的可维护性,性能和可伸缩性。
答案 2 :(得分:1)
您可以将密钥包装到允许修改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;
}
请注意,我们必须提供自定义比较器对象,因为我们(大概)想按键值而不是指针值进行排序。
要清楚,这不是unique_ptr
的含义,并且智能指针的const
语义(遵循常规指针的语义)从这个角度来看有点倒退(为什么我可以从const
对象中获得非const
引用吗?linter可能会抱怨这种用法...),但这样做确实有用。当然,裸露的指针也可以使用相同的方法(其中T* const
可以更改T
的值,但指针的位置不变,而const T*
可以更改其位置,但{{ 1}}),但这模仿了原始代码的所有权/生命周期模型。
不用说,这为打破地图不变式(打破键的排序方式)打开了大门,因此在使用它之前请三思。但是与直接T
直接输入密钥不同,它没有UB。
答案 3 :(得分:1)
std::map
和其他标准关联容器无法提供一种无需删除和添加元素的方式来完成此操作的方法,这可能会导致树重新平衡的副作用。您可以通过多种方式(例如使用mutable
成员)遍历地图键的常数,但是要完全由您自己决定,以确保您实际上没有破坏键的顺序。
如果您需要这种效率,但需要更多的安全性,则可以考虑将容器更改为boost::multi_index_container
。
std::map<K,V>
类似于:
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(); });