码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2018-04-07 16:20:44      阅读:203      评论:0      收藏:0      [点我收藏+]

标签:种类型   符号整型   effect   推断   精简   目的   方便   ring   alt   

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, 指定是一种无符号整型类型

      上述代码在32位windows上,unsigned和std::vector<int>::size_type都是32位,但是在64位windows上,unsigned是32位而std::vector<int>::size_type是64位,因此在不同的机器上运行相同的代码可能会出错,这种与底层系统耦合性较强的错误不应该出现。

    • 因此,正确的用法如下:
      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_map的key部分是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:
      std::vector<bool> features(const Widget& w);//提取出Widget对象的特征,并以vector<bool>的形式返回,每一个bool代表该feature是否存在
      
      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>::reference和std::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的优点,同时保证程序的正确性。

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

标签:种类型   符号整型   effect   推断   精简   目的   方便   ring   alt   

原文地址:https://www.cnblogs.com/burningTheStar/p/8733006.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!