Item5 Prefer auto to explicit type declarations【Effective Modern C++中文...

[复制链接]

该用户从未签到

759

主题

763

帖子

4660

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4660
发表于 2018-1-9 08:52:16 | 显示全部楼层 |阅读模式

想要查看内容赶紧注册登陆吧!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
写C/C++的程序员都知道定义一个变量如果没有给初值会导致这个变量的值是未定义的,这往往是bug的源泉,在使用容器的迭代器的时候,需要定义一个迭代器变量,这个变量的类型很冗长,例如下面的代码应该经常可以碰到。
  1. vector<int>::iterator it = xxx.begin();
  2. vector<map<int,int> >::iterator
  3. .....
复制代码


到了C++11,算是对上面碰到的问题有了一个比较折中的解决方案了,如下:
  1. auto x1;    //没有初始化会报错
  2. auto it = xxx.begin();  //没有冗长的类型名了
复制代码
auto在Item2中介绍过,利用了类型推导实现。除了上面提到的两个优点外,auto还有一些其他的不为人知的优点:
  1. auto f1 = [](const std::unique_ptr<int>& p1,const std::unique_ptr<int>& p2) {
  2.     return *p1 < *p2;
  3.   };

  4.   std::function<bool(const std::unique_ptr<int>&,std::unique_ptr<int>&)> f2 = [](
  5.     const std::unique_ptr<int>& p1,const std::unique_ptr<int>& p2) {
  6.     return *p1 < *p2;
  7.   };
复制代码
上面的这个场景,使用auto和std::function,会有显著的不同,f1的类型就是一个闭包类型,而f2是一个std::function模板类(可以通过Item4提到的方法来打印输出f1和f2的类型),他内部会使用一块内存来保存闭包,但是这块内存的大小是固定的,因此会随着闭包大小的增大进而会进行内存的分配所以可能会产生内存分配失败的异常,下面是截取了std::function的部分实现的源码,可读性不佳,不过可以通过注释大致了解这一事实。
  1. // Clone a location-invariant function object that fits within
  2.   // an _Any_data structure.
  3.   static void
  4.   _M_clone(_Any_data& __dest, const _Any_data& __source, true_type)
  5.   {
  6.     new (__dest._M_access()) _Functor(__source._M_access<_Functor>());
  7.   }

  8.   // Clone a function object that is not location-invariant or
  9.   // that cannot fit into an _Any_data structure.
  10.   static void
  11.   _M_clone(_Any_data& __dest, const _Any_data& __source, false_type)
  12.   {
  13.     __dest._M_access<_Functor*>() =
  14.       new _Functor(*__source._M_access<_Functor*>());
  15.   }
复制代码
f1,f2在运行的时候开销也不同,f1因为是闭包可以直接运行,而f2其实是间接的调用了内部保存的闭包。因此综合来说auto更适合,不仅仅代码清晰,开销也小。在C++14中,lambda的参数也可以使用auto了,f1可以简写成如下的形式
  1. auto f1 = [](const auto& p1,const auto& p2) {
  2.     return *p1 < *p2;
  3.   };
复制代码
接着让我们来看看另外一个在C++中不为人知的秘密,通过auto可以轻松的避免这类问题的发生。
  1. std::unordered_map<std::string,int> m;
复制代码
上面是一个key为std::string,value的类型是int的unordered_map其实就是一个std::pair<std::string,int>类型,我想这应该是大多数人都是这么认为的吧,毕竟所见即所得。我们能看见的就是key是std::string,value是int但是其实它的key是const的。也就是实际上它是std::pair<const std::string,int>具体的细节可以参见cppreference.因此这带来了一些问题,如下:
  1. for(const std::pair<std::string,int>& p : m) {
  2.         //do something
  3. }
复制代码
相信大多数人都会写出上面的代码,如果不知道map的key其实是const的很难发现上面的问题,p没办法引用m中的元素,为了可以成功运行,编译器通过将std::pair<const std::string,int>转化成一个临时的std::pair<std::string,int>对象然后使用p引用这个临时的对象,循环结束后再释放这个临时的对象。这样就造成了大量临时对象不可避免的构造和析构的成本。可以通过下面的方法验证上面的结论:
  1. #include <unordered_map>
  2. #include <iostream>


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

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

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

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

使用道具 举报

快速回复高级模式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表