21.禁止直接修改set或mutiset的key

前言



正如所有标准关联容器,set和multiset保证元素有序,而且容器的行为之所以能够正确也是建立在内部元素有序的基础之上。如果你修改了关联容器里的元素的key,那么元素的有序可能得不到保证。


修改map的key

</br>
本节标题只提到了set与mutiset,因此有读者会发生疑问:难道map或mutimap的key就可以改变吗?实则不然:

1
2
3
4
5
6
map<int, string> m;
...
m.begin()->first = 10; //error
multimap<int, string> mm;
...
mm.begin()->first = 20; //error

map的key根本无法改变,任何试图改变其key的操作都无法通过编译,这也就是本节不涉及map的原因。
之所以map的key无法改变,那是因为map<K,V>或者multimap<K,V>类型的对象中元素的类型是pair<const K,V>,const保证了其key不可赋值。


为何set的key不能设为const

</br>
对于set<T>multiset<T>类型的对象来说,储存在容器里的元素类型只不过是T,并非constT。因此,set或multiset里的元素可能在你想要的任何时候改变。那这个时候问题来了,为何set内的元素不能设置为const呢?

问题实例

假设存在一个关于雇员的class:

1
2
3
4
5
6
7
8
9
10
class Employee {
public:
...
const string& name() const;
void setName(const string& name);
const string& getTitle() const;
void setTitle(string& title);
int idNumber() const;
...
};

假定雇员ID唯一,建立一个内部元素为Employee对象的set,显然应该以ID来排序set:
1
2
3
4
5
6
7
8
struct IDNumberLess:
public binary_function<Employee, Employee, bool> {
bool operator()(const Employees lhs,const Employee& rhs) const{
return lhs.idNumber() < rhs.idNumber();
}
};
typedef set<Employee, IDNumberLess> EmpIDSet;
EmpIDSet se;

在此实例中,ID是set的key,其他东西自然可以任意更改:
1
2
3
4
5
Employee selectedID;
auto i = se.find(selectedID);
if (i != se.end()){
i->setTitle("Corporate Deity");//更换头衔
}

我们可以自由地改变set中对象的某一部分成员,那说明set内部的元素并没有用const修饰,那也就是说,set内部的一切都可以改变。改变set的key是存在巨大风险的,但编译器允许这种行为。

(Effective STL的作者接下来花了大量的篇幅讲述映射,我认为其内容并非一定需要了解)


如何安全地改变set内部的元素

</br>
想要安全地更改set内部的元素,必须按照以下流程执行

  1. 定位到需要修改的元素
  2. 拷贝并修改
  3. 从set中删除原元素
  4. 插入新元素
1
2
3
4
5
6
7
8
9
10
EmpIDSet se; 
Employee selectedID;
...
auto i =se.find(selectedID);
if(i!=se.end()){
Employee e(*i); //copy
se.erase(i++);//自增保证迭代器有效
e.setTitle("Corporate Deity");//修改副本
se.insert(i, e);//在原先位置插入新值
}

笔者认为在关联容器内部指定位置插入的做法不足取,但本节也仅仅只是权宜之举。对于set,记住不要试图去修改key就好了。