9.慎重选择删除元素的方法

删除某个值

假设我们有一个容器,其内部元素类型为int.现在需要删除容器c内所有值为1963的元素,值得注意的是,完成这项任务的方法因不同的容器类型而不同,无通用解

连续内存容器(vector,deque,string)

最好的删除操作是erase—remove惯用法:

1
c.erase(remove(c.begin(),c.end(),1963),c.end());

list

list也可以用erase_remove,但是最好直接使用remove:

1
c.remove(1963);

标准关联容器

标准关联容器没有remove成员函数,其删除操作的正确做法是使用erase

1
c.erase(1963);

这种做法兼具高效性与正确性,对数时间开销,同时关联容器的erase基于等价而非相等。(详见Effective STL 19)


删除所有符合条件的值

序列容器(连续内存容器与list)

我们只需要把remove改为remove_if

1
2
3
bool badValue(int);
c.erase(remove_if(c.begin(),c.end(),badValue),c.end());//连续内存容器
c.remove_if(badValue); //list

关联容器

对于关联容器,一般有两种方法来处理这一情况,一个容易编码,一个高效。

容易编码的方法

其原理为:remove_copy_if把我们需要的值拷贝到新容器中,然后把原容器内容与新容器的交换:

1
2
3
4
5
AssocContainer<int> c; 
...
AssocContainer<int> goodValues; //临时容器
remove_copy_if(c.begin(), c.end(), inserter(goodValues, goodValues.end()), badValue);
c.swap(goodValues); // 交换c和goodValues

这种方法的缺点是它拷贝了所有不删除的元素,直接造成了效率极低。

高效的方法

其直接从容器中删除元素,不过关联容器没有remove_if之类的成员函数,所以必须通过循环来完成,首先给出一个错误案例:

1
2
3
4
5
AssocContainer<int> c;
...
for (auto i = c.begin(); i!= c.end(); ++i) {
if (badValue(*i)) c.erase(i);//未考虑迭代器失效
}

为了避免迭代器失效,我们必须保证在调用erase之前就得到了c中下一个元素的迭代器,所以有正确写法如下:
1
2
3
4
5
6
AssocContainer<int> c;
...
for (auto i = c.begin(); i != c.end();/*nothing*/ ){
if (badValue(*i)) c.erase(i++); //如果需要删除,删除当前位置并自增迭代器
else ++i;//不需要删除则直接递增
}


删除元素后记录日志

关联容器

对于关联容器,只要在循环内部增加一条语句即可:

1
2
3
4
5
6
7
8
9
10
ofstream logFile; //日志文件
AssocContainer<int> c;
...
for (auto i = c.begin();i !=c.end();){
if (badValue(*i)){
logFile << "Erasing " << *i <<'\n'; //写日志文件
c.erase(i++); // 删除元素
}
else ++i;
}

连续内存容器

与关联容器不同,连续容器的erase不仅令当前迭代器失效,也同时令后面的迭代器失效。所以我们必须利用erase返回被删除元素之后的元素的迭代器的特性,完成操作:

1
2
3
4
5
6
7
for (auto i = c.begin();i != c.end();){
if (badValue(*i)){
logFile << "Erasing " << *i << '\n';
i = c.erase(i);//利用返回值,也是C++ primer描述容器删除时使用的方法
}
else ++i;
}

list

对于list,上述两种方法都行。


总结:

  1. 去除容器内有特定值的对象,连续容器使用erase-remove,list使用remove,关联容器使用erase
  2. 去除满足某个判定式的所有对象,连续容器使用erase-remove_if,list使用remove_if,关联容器则使用remove_cooy_if与swap,或者使用循环(记得后置递增)
  3. 需要在循环中做操作时,连续内存容器记得要利用erase返回值更新迭代器,关联容器同2。