我们对map中的修改value操作可以是如下的:
auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
myMap.find("two")->second = 22;
但是如果想修改key操作,则下面这段代码是错误的:
auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
myMap.find("two")->first = "dos";
如果是map<int, int>
,则会报这样的错误:
error: assignment of read-only member ‘std::pair<const int, int>::first’
m.find(1)->first=3;
修改std::map
的key值的问题
在std::vector
、std::map
和std::set
这些序列容器中,需要提供两个保证:
- 元素应该以有序的顺序来存储
- 确保元素是唯一的
但是在map的情况下,排序顺序很方便,以便找到与对数复杂度的键关联的值。为了保持这些不变性,容器std::map和std::set需要对它们的值在集合内的相对位置进行一些控制。
如果只是使用迭代器来修改值,例如之前使用的示例,则不会通知该容器。 这将使其结构不一致并破坏不变性。所以是read-only member
C++17中的key值修改
修改key的策略是通过容器携带的接口,而不是通过迭代器进行修改
c++17中给关联容器提供了一个方法:extract
,这提供了保存容器元素的节点:
auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
auto const node = myMap.extract("two");
extract对容器具有修改作用:映射不再包含节点。 如果我们在调用提取之前和之后检查大小:
auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
std::cout << myMap.size() << '
'; // 3
auto node = myMap.extract("two");
std::cout << myMap.size() << '
'; // 2
所以此时node并不是和map中的元素相关联,所以不会破坏map的结构,修改key值就可以:
node.key() = "dos";
而这个node并没有提供value的修改方法,因为在map中直接操作会更加方便。
修改key的值后,需要将node重新插回到map结构中:
myMap.insert(std::move(node));
这里必须写上std::move
,因为node只有移动构造,并没有拷贝构造函数。
当请求的node并不存在的时候
auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
auto node = myMap.extract("four");
node仍然是个有效的对象,我们可以将其用insert插入,但是不能访问它的key();所以最有效的措施应该是在extract之前判断一下是否为空:
auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
auto node = myMap.extract("two");
if (!node.empty())
{
node.key() = "dos";
myMap.insert(std::move(node));
}
C++17之前怎么做
在C++17之前,做法就是erase key所在的元素然后再更新后插入,但是这样的做法非常的低效而且不直接。
封装node
修改一个map的key值,比提取一个node、修改相应的值、最后插回map中这种表达会更清晰,虽然后者是前者的实现思想。所以我们可以将后者进行封装,抽象为前者的表达:
template<typename Container>
void replaceKey(Container& container,
const typename Container::key_type& oldKey,
const typename Container::key_type& newKey)
{
auto node = container.extract(oldKey);
if(!node.empty())
{
node.key() = newKey;
container.insert(std::move(node));
}
}