C++常见避坑指南( 五 )

上面几类容器同样的遍历删除元素,只有vector报错crash了,map和list都能正常运行 。其实vector调用erase()方法后 , 当前位置到容器末尾元素的所有迭代器全部失效了 , 以至于不能再使用 。
迭代器的失效问题:对容器的操作影响了元素的存放位置,称为迭代器失效 。迭代器失效的情况:
● 当容器调用erase()方法后,当前位置到容器末尾元素的所有迭代器全部失效 。
● 当容器调用insert()方法后,当前位置到容器末尾元素的所有迭代器全部失效 。
● 如果容器扩容 , 在其他地方重新又开辟了一块内存,原来容器底层的内存上所保存的迭代器全都失效 。
迭代器失效有三种情况,由于底层的存储数据结构,分三种情况:
序列式迭代器失效,序列式容器(std::vector和std::deque),其对应的数据结构分配在连续的内存中,对其中的迭代器进行insert和erase操作都会使得删除点和插入点之后的元素挪位置,进而导致插入点和删除掉之后的迭代器全部失效 。可以利用erase迭代器接口返回的是下一个有效的迭代器 。
链表式迭代器失效,链表式容器(std::list)使用链表进行数据存储,插入或者删除只会对当前的节点造成影响,不会影响其他的迭代器 。可以利用erase迭代器接口返回的是下一个有效的迭代器 , 或者将当前的迭代器指向下一个erase(iter++) 。
关联式迭代器失效,关联式容器,如map, set,multimap,multiset等,使用红黑树进行数据存储,删除当前的迭代器,仅会使当前的迭代器失效 。erase迭代器的返回值为 void(C++11之前),可以采用erase(iter++)的方式进行删除 。值得一提的是 , 在最新的C++11标准中,已经新增了一个map::erase函数执行后会返回下一个元素的iterator,因此可以使用erase的返回值获取下一个有效的迭代器 。
在实现上有两种模板,其一是通过 erase 获得下一个有效的 iterator , 使用于序列式迭代器和链表式迭代器(C++11开始关联式迭代器也可以使用)
for (auto it = elements.begin(); it != elements.end(); ) {    if (ShouldDelete(*it)) {        it = elements.erase(it); // erase删除元素 , 返回下一个迭代器    } else {        it++;    }}其二是 , 递增当前迭代器 , 适用于链表式迭代器和关联式迭代器 。
for (auto it = elements.begin(); it != elements.end(); ) {    if (ShouldDelete(*it)) {        elements.erase(it++);     } else {        it++;    }}对象拷贝在众多编程语言中C++的优势之一便是其高性能,可是开发者代码写得不好(比如:很多不必要的对象拷贝),直接会影响到代码性能,接下来就讲几个常见的会引起无意义拷贝的场景 。
for循环:
std::vector<std::string> vec;for(std::string s: vec) {}// orfor(auto s: vec) {}这里每个string都会被拷贝一次 , 为避免无意义拷贝可以将其改成:
for(const auto& s: vec) 或者 for (const std::string& s: vec)
lambda捕获
// 获取对应消息类型的内容std::string GetRichTextMessageXxxContent(const std::shared_ptr<model::Message>& message, const std::map<model::MessageId, std::map<model::UserId, std::string>>& related_user_names, const model::UserId& login_userid, bool for_message_index) { // ... // 解析RichText内容 return DecodeRichTextMessage(message, [=](uint32_t item_type, const std::string& data) {  std::string output_text;  // ...  return output_text;  });}上述代码用于解析获取文本消息内容,涉及到富文本消息的解析和一些逻辑的计算,高频调用,他在解析RichText内容的callback中直接简单粗暴的按值捕获了所有变量,将所有变量都拷贝了一份,这里造成不必要的性能损耗,尤其上面那个std::map 。这里可以改成按引用来捕获,规避不必要的拷贝 。
lambda函数在捕获时会将被捕获对象拷贝,如果捕获的对象很多或者很占内存,将会影响整体的性能,可以根据需求使用引用捕获或者按需捕获:


推荐阅读