Item5 Prefer auto to explicit type declarations

   写C/C++的程序员都知道定义一个变量如果没有给初值会导致这个变量的值是未定义的,这往往是bug的源泉,在使用容器的迭代器的时候,需要定义一个迭代器变量,这个变量的类型很冗长,例如下面的代码应该经常可以碰到。

vector<int>::iterator it = xxx.begin();
vector<map<int,int> >::iterator
.....

到了C++11,算是对上面碰到的问题有了一个比较折中的解决方案了,如下:

auto x1;    //没有初始化会报错
auto it = xxx.begin();  //没有冗长的类型名了

   auto在Item2中介绍过,利用了类型推导实现。除了上面提到的两个优点外,auto还有一些其他的不为人知的优点:

  auto f1 = [](const std::unique_ptr<int>& p1,const std::unique_ptr<int>& p2) {
    return *p1 < *p2;
  };

  std::function<bool(const std::unique_ptr<int>&,std::unique_ptr<int>&)> f2 = [](
    const std::unique_ptr<int>& p1,const std::unique_ptr<int>& p2) {
    return *p1 < *p2;
  };

   上面的这个场景,使用autostd::function,会有显著的不同,f1的类型就是一个闭包类型,而f2是一个std::function模板类(可以通过Item4提到的方法来打印输出f1和f2的类型),他内部会使用一块内存来保存闭包,但是这块内存的大小是固定的,因此会随着闭包大小的增大进而会进行内存的分配所以可能会产生内存分配失败的异常,下面是截取了std::function的部分实现的源码,可读性不佳,不过可以通过注释大致了解这一事实。


  // Clone a location-invariant function object that fits within
  // an _Any_data structure.
  static void
  _M_clone(_Any_data& __dest, const _Any_data& __source, true_type)
  {
    new (__dest._M_access()) _Functor(__source._M_access<_Functor>());
  }

  // Clone a function object that is not location-invariant or
  // that cannot fit into an _Any_data structure.
  static void
  _M_clone(_Any_data& __dest, const _Any_data& __source, false_type)
  {
    __dest._M_access<_Functor*>() =
      new _Functor(*__source._M_access<_Functor*>());
  }

   f1,f2在运行的时候开销也不同,f1因为是闭包可以直接运行,而f2其实是间接的调用了内部保存的闭包。因此综合来说auto更适合,不仅仅代码清晰,开销也小。在C++14中,lambda的参数也可以使用auto了,f1可以简写成如下的形式

  auto f1 = [](const auto& p1,const auto& p2) {
    return *p1 < *p2;
  };

接着让我们来看看另外一个在C++中不为人知的秘密,通过auto可以轻松的避免这类问题的发生。

std::unordered_map<std::string,int> m;

   上面是一个key为std::string,value的类型是intunordered_map其实就是一个std::pair<std::string,int>类型,我想这应该是大多数人都是这么认为的吧,毕竟所见即所得。我们能看见的就是key是std::string,value是int但是其实它的key是const的。也就是实际上它是std::pair<const std::string,int>具体的细节可以参见cppreference.因此这带来了一些问题,如下:

for(const std::pair<std::string,int>& p : m) {
        //do something
}

   相信大多数人都会写出上面的代码,如果不知道map的key其实是const的很难发现上面的问题,p没办法引用m中的元素,为了可以成功运行,编译器通过将std::pair<const std::string,int>转化成一个临时的std::pair<std::string,int>对象然后使用p引用这个临时的对象,循环结束后再释放这个临时的对象。这样就造成了大量临时对象不可避免的构造和析构的成本。可以通过下面的方法验证上面的结论:

#include <unordered_map>
#include <iostream>


int main() {
  int p;
  std::unordered_map<std::string,int> m;
  m["test"]  = 1;
  std::unordered_map<std::string,int>::iterator it = m.find("test");
  printf("address: %p\n",*it);

  for(const std::pair<std::string,int>&p : m) {
    printf("address: %p\n",p);
  }

  for(const auto&c : m) {
    printf("address: %p\n",c);
  }

}

   你会发现itc的地址是一样,和p的地址不一样,如果把p的key类型换成const的则it,c,p的地址都是一样的。像上面这样的不为人知的小例子还有很多,如果你不清楚内幕很容易导致你的代码效率低下,但是如果使用auto就可以在一定程度上避免这些坑。一直在说auto的优点,现在来说说auto的一些不足之处吧。在Item2中提到过,auto在一些情况下得到的类型并不是实际的类型,具体可以参考Item2,有的人认为使用auto后会导致了代码不易读,因为不知道变量的实际类型,这个其实在Item4中提到过,可以借助于IDE和其他的一些办法,但是我觉得只要你不滥用auto即可。

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值