我們對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() << '\n'; // 3
auto node = myMap.extract("two");
std::cout << myMap.size() << '\n'; // 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));
}
}
