[Effective Modern C++(11&14)]Chapter 2: auto

1.更多的使用auto而不是显式类型声明

  • 将大段声明缩减成auto
    • 例如:
typename std::iterator_traits<It>::value_type currValue = *b;
auto currValue = *b;
  • 使用auto可以防止变量未初始化
    • 例如:
int x1; //正确,但是未初始化
auto x2; //错误,没有初始化
auto x3 = 3; //正确,声明并初始化
  • 在模板函数中可以使用auto来完成变量的自动类型推导
    • 例如:
template<typename It>
void dwim(It b, It e)
{
     for(; b!=e; ++b)
     {
          auto currValue = *b;
          ...
     }   
}
  • 使用auto来自动推断lambda表达式的返回结果类型
    • 例如:
auto derefLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2){ return *p1 < *p2; };
    • 相比之下,使用function对象来保存上述结果,代码如下:
std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>& )> 
                 derefUPLess = [](
    const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2){ return *p1 < *p2; };
    • 这两种表达形式的区别是:
      • auto声明的变量,占用和lambda表达式同样大的内存空间
      • 使用std::function声明的变量对于任何函数都是固定大小的空间,如果空间不足,就会在堆上申请内存来存储这个闭包。另外,由于限制内联,函数对象不得不产生一次间接函数调用。
      • 结果是:std::function对象通常使用更多的内存,执行速度也比auto要慢。
  • 使用auto来避免"type shortcuts"
    • 例如:
std::vector<int> v;
...
unsigned sz = v.size();// v.size()返回值类型是 std::vector<int>::size_type, 指定是一种无符号整型类型
    • 上述代码在32windows上,unsignedstd::vector<int>::size_type都是32位,但是在64windows上,unsigned32位而std::vector<int>::size_type64位,因此在不同的机器上运行相同的代码可能会出错,这种与底层系统耦合性较强的错误不应该出现。
    • 因此,正确的用法如下:
auto sz = v.size(); 
  • 使用auto声明变量来避免类型不匹配时的隐式转换带来的额外代价
    • 例如:
std::unordered_map<std::string, int> m;
...
for(const std::pair<std::string, int>& p : m)
{
    ... // do somethins with p   
}
    • 上述代码的毛病在于:std::unordered_mapkey部分是const属性,因此哈希表中的std::pair类型实际上应该是std::pair<const std::string, int>。但是上述循环中声明的是一个const std::pair<std::string, int>,因此编译器不得不在这两种类型中做一个转换,首先为了得到一个std::pair<std::string, int>,编译器需要从m中对每个对象进行一次拷贝,创建一系列临时变量,然后再将这些临时变量依次绑定到引用p,在循环结束时,这些临时变量再被编译器进行销毁。
    • 正确的做法应该是:
for( const auto& p : m)
{
    ... // as before
}
  • 有关代码可读性的建议:
    • 如果使用显示类型声明使得代码更清晰且更容易维护,那么就应该使用显示类型声明,否则就应该试着使用auto
    • 通过auto声明的变量,如果想要方便获取是什么类型,可以通过命名规则来间接表示类型。

2.当auto推导出错误类型时使用显式类型初始化方式

  • 当表达式返回的类型是代理类的类型时,不能使用auto
    • 例1:
//提取出Widget对象的特征,并以vector<bool>的形式返回
//每一个bool代表该feature是否存在
std::vector<bool> features(const Widget& w);

Widget w;
...
//访问特定的feature标志位5
bool highPriority = features(w)[5];//(1)
auto highPriority_alt = features(w)[5];//(2)
...
//根据上述标志位的值来处理Widget对象
processWidget(w, highPriority);//(3)
processWidget(w, highPriority_alt);//(4)
...
    • 上述代码中(1)(3)可以正常运行,但是(2)(4)就会出现未定义行为,这是为什么?
    • 因为std::vector<bool>虽然持有bool,但是operator[]作用于vector<bool>时,并不会返回vector容器中元素的引用([]操作返回容器内元素的引用对于其他类型都适用,唯独bool不适用),而是返回一个std::vector<bool>::reference类型的对象。为什么会存在这种类型的对象呢?因为vector<bool>是通过紧凑的形式来表示bool值,每一个bit代表一个bool。这给[]操作造成了困难,因为对于std::vector<T>,[]操作理应返回的是一个T&对象,但是C++禁止返回对bit的引用,也就是不能返回bool&,那么就得想办法返回一个对象来模拟bool&的行为。因此,std::vector<bool>::reference对象就出现了,它可以在需要的地方自动从bool&转换成bool类型。所以,在(1)中,隐式自动转换是成功的,而在(2)中,auto自动接收了std::vector<bool>::reference对象的类型,没有发生转换,而该对象实际指向的是一个临时std::vector<bool>对象的内部内存地址,当执行完这条语句后,该临时对象被销毁,那么auto保存的地址也就属于悬空地址。在(4)中就会出发未定义行为。
    • 代理介绍
      • std::vector<bool>::reference是代理类的一个例子,它们存在的目的是模拟和增强其他类型的行为。例如标准库中智能指针类型也是代理类的例子,它们负责对原始指针指向资源的管理。
      • 有一些代理类是对用户可见的,比如std::shared_ptr,std::unique_ptr。而另一些代理类则是用户不可见的,比如: std::vector<bool>::referencestd::bitset::reference
      • C++某些库中使用的叫做表达式模板的技术也属于这个范畴,这种库是为了改善数值计算代码的效率。例2:
Matrix m1, m2, m3, m4;
...
Matrix sum = m1 + m2 + m3 + m4;
      • 如果operator+操作返回的是一个代理类比如:Sum<Matrix, Matrix>而不是结果本身也就是Matrix对象,那么这样就可以高效计算这个表达式。
      • 因为对象的类型会被编码到整个初始化表达式,比如:Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>
    • 一般性规则,不可见的代理类不适用与auto,因为代理类对象一般只存活于一条语句内,因此创建代理类对象的变量违反了基本库设计假设。
  • auto推到出代理类类型时,需要对表达式做代理类类型到实际类型的静态转换,而不是弃用auto
    • 针对上面的例1:
auto highPriority = static_cast<bool>(features(w)[5]);
    • 针对上面的例2:
auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);

3.总结

auto自动类型推导可以精简代码,避免隐式转换带来开销,同时增强程序可移植性和减少重构复杂性;但也由于与隐式代理类的冲突,造成了一些潜在问题,但是这些问题不是auto引起的,而是代理类本身的问题,因此显式静态类型转换可以保留auto的优点,同时保证程序的正确性。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏LIN_ZONE

JavaScript经典作用域问题(转载)

注:本文转自 javascript经典面试题 全局变量和局部变量 变量作用域 如需转载,请注明出处:https://www.cnblogs.com/zhuch...

912
来自专栏决胜机器学习

PHP面向对象核心(一)——序列化与魔术方法

PHP面向对象核心(一) (原创内容,转载请注明来源,谢谢) 一、对象存储——serialize与unserialize 对象是存储类的属性的键值对,类似于数...

41010
来自专栏向治洪

Promise机制

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的...

21810
来自专栏lgp20151222

理解一下策略模式,工厂模式

开发遇到了一个工厂模式和策略模式合起来的代码,觉得真的是写的太好了,所以打算了解下这两个模式的区别。

1311
来自专栏企鹅号快讯

看完这篇文章我知道至少85%的人是没有入门Python的!花两周整理

以前刚学编程的时候就对Python略有耳闻,不过学校只有C,C++,Java,C#。和PHP有句"PHP是最好的语言" 这种家喻户晓的骚话一样,Python也有...

2907
来自专栏极客慕白的成长之路

JavaScript代码压缩细节

对于Javascript来说,提高网络下载的性能最直接的方法就是把JS文件体积减小。

972
来自专栏CRPER折腾记

ES6折腾记- 模板字符串

总体来说,模板字符串的出现了,让我们的字符串拼接写的更加优美了;相当简易实用;但是这货并不是万能的,有部分unicode编码字符会造成编译报错

1053
来自专栏用户2442861的专栏

Java中Synchronized的用法

原文:http://blog.csdn.net/luoweifu/article/details/46613015 作者:luoweifu 转载请标名...

681
来自专栏PHP实战技术

你应该这个姿势学习PHP(1)

1、addslashes addslasehes($string) 对字符串进行编译转义 应用场景:能防止sql的注入(当然并不完全是可以,我们可以使用pdo...

42210
来自专栏java一日一条

(转)Java中的System类

System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。

982

扫码关注云+社区

领取腾讯云代金券