c++ Code Review中注意的问题

411 阅读3分钟

简介

给策略同学过c++代码时,总结了一些问题,包括三类

  1. 避免影响性能的问题
  2. 避免宕机问题
  3. 避免丑陋代码和不标准的格式 本文介绍下CR中遇到的各种具体问题

避免影响性能

避免容器里的重复查找

看这段代码,假设map_std::map,下面这段代码在map_里搜索了两次,第一次是find(),第二次是map_[id],这里犯了重复查找的问题

if (map_.find(id) != map_.end()) {
    map_[id]++;
}

find()的结果保存下来,避免第二次查找,改成下面这样:

auto it = map_.find(id)
if (it != map_.end()) {
    *it++;
}

修改后的代码就只有find()的时候查找了一次

注意要求对方在for循环中使用const和引用

for (auto iter : dict())

如果不改变元素,需要改成

for (const auto & conf : dict())

使用现成容器函数

现成的容器函数往往能替换掉大段的 for循环 + if判断

常用的有:

  • find_if()
  • count_if()
  • boost::range::remove_erase_if

具体使用见这篇文章

避免无用的深拷贝

避免无用的深拷贝,当声明一个类对象时,例如

std::string s2 = s1;

这个属于高频问题,看到非基础类型的定义时就应该虎躯一震,看看这里是不是用引用就行。许多c++开发经验的同学都是完全没有这个意识的

std::string& s2 = s1;

避免宕机

在已有循环中添加代码,注意break,continue

已经有一大段循环的代码,当别人往里面添加逻辑时,要注意他加入的break,continue,return语句是否合理,后面有没有必须要走的逻辑,被他这句break跳过就完蛋了,比如下面代码,加的break会破坏后面的重要逻辑L。

所以遇到这种代码,要展开看一下后续逻辑,不要只看新增的代码

for (...){
    // 一堆代码...
     **在这增加代码**
     if (xxx){break;}
    // 一堆代码...
    // 重要逻辑L
}

看到除号,注意除0判断

CR的时候看到除号要下意识看看分母有没有可能为0,有没有做除0判断

注意下标越界

  • 对map取下标map[key],要注意这个key是否一定存在,不存在的话加没加map.find(key)==map.end()判断
  • 对数组取下标,例如vector[index],要注意index是否为负数或者>=size

编码规范&&美化代码

注意临时变量是否有用到,或者函数中有无用变量

有时候声明一个变量却没有用到,或者对他一通操作,但是函数返回值跟这个无关,这属于无用的变量,需要避免

简化大段的 if else 嵌套

直接看代码吧:

while (!query_vec.empty()) {
  std::string query = query_vec.back();
  if (flag_) {
    if (boost::starts_with(query, "#") && boost::ends_with(query, "#")) {
      query_vec.pop_back();
      continue;
    } else {
      info_vec.push_back(query);
    }
  } else {
    info_vec.push_back(query);
  }
  query_vec.pop_back();
}

上面这段代码及其丑陋,读起来太难受了 简化下来的代码如下:

while (!user_search_querys_vec.empty()) {
  std::string query = query_vec.back();
  if (!flag_ 
       || (flag_ 
           && boost::starts_with(query, "#") 
           && boost::ends_with(query, "#")) {
      info_vec.push_back(query);
  }
  query_vec.pop_back();
}

好看多了吧。。。

容易忽略的BUG

慎用Fa() && Fb();

以下代码需要注意的是,如果Fa()返回false,Fb就不会执行了,这么写之前你需要考虑清楚,Fa失败的情况下你想不想让Fb仍然执行

bool F() {
    return Fa()&&Fb();
}

使用std::function时,function中的参数要注意是传值还是引用

例如以下代码:

class ClassWithMap {
public:
bool Visit(std::function<bool(std::unordered_map<int, int>&)>&& fun) {
    fun(map_);
}

private:
std::unordered_map<int, int> map_;
};

int main () {
    ClassWithMap class_with_map;
    auto fun = [&](std::unordered_map<int, int> m) {
       m.emplace(1,1);
    };
    class_with_map.visit(std::move(fun)); // 想要用fun对class_with_map中map_里的值做一些修改
    return 0;
}

上面这段代码,可以看到fun的类型是std::function<bool(std::unordered_map<int, int>)>,是传值的,调用visit并不能修改class_with_map中的值,但是这段代码仍然能过通过编译,因为std::function构造函数接收的类型广泛,如果想要在编译时报错,可以加入下面这句禁用掉传值调用

bool Visit(std::function<bool(std::unordered_map<int, int>)>&& fun) = delete;