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

      上述代码在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的优点,同时保证程序的正确性。

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

时间: 2024-10-11 20:14:02

[Effective Modern C++(11&14)]Chapter 2: auto的相关文章

[Effective Modern C++(11&amp;14)]Chapter 3: Moving to Modern C++

1. Distinguish between () and {} when creating objects C++11中,初始化值的指定方式有三种:括号初始化,等号初始化和花括号初始化:其中花括号初始化是为了解决C++98的表达能力而引入的一种统一初始化思想的实例. 等号初始化和花括号初始化可以用于非静态成员变量的初始化 class Widget { ... private: int x {0}; // ok int y = 0; // ok int z(0); // error }; 括号初

[C++11] Effective Modern C++ 读书笔记

本文记录了我读Effective Modern C++时自己的一些理解和心得. item1:模板类型推导 1)reference属性不能通过传值参数传入模板函数.这就意味着如果模板函数需要一个reference类型的参数,必须在模板声明中将其声明为reference,否则,即使使用一个reference类型的变量调用模板函数,类型推导的结果将不带reference属性. 2)constant和volatile属性也不能通过传值参数传入模板函数,但是可以通过reference参数传入这些属性. 3

《Effective Modern C++》翻译--条款2: 理解auto自动类型推导

条款2: 理解auto自动类型推导 如果你已经读过条款1关于模板类型推导的内容,那么你几乎已经知道了关于auto类型推导的全部.至于为什么auto类型推导就是模板类型推导只有一个地方感到好奇.那是什么呢?即模板类型推导包括了模板.函数和参数,而auto类型推断不用与这些打交道. 这当然是真的,但是没关系.模板类型推导和auto自动类型推导是直接匹配的.从字面上看,就是从一个算法转换到另一个算法而已. 在条款1中,阐述模板类型推导采用的是常规的函数模板: template<typename T>

《Effective Modern C++》读书笔记 Item 2 auto的类型推导

注意: 还要学习一个 ↑↑↑↑ 这样的方框里的片段完全不来自于原书,而是我自己的理解. Item 2 Understand auto type deduction - auto类型推导 在C++11之前,auto 关键字一直是用于声明自动储存类型的变量时使用的,基本上没有什么实际作用,地位和 export 关键字(用于向编译单元之外导出模板,也在C++11中被取消)类似. 在C++11中,auto 终于不再废材,终于具备了类似C#中 var 关键字的效果,可以自动推导出变量的类型,可以少打几个字

《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导

条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇.那是什么呢?即模板类型推导包含了模板.函数和參数,而auto类型判断不用与这些打交道. 这当然是真的.可是没关系. 模板类型推导和auto自己主动类型推导是直接匹配的. 从字面上看,就是从一个算法转换到还有一个算法而已. 在条款1中.阐述模板类型推导採用的是常规的函数模板: template<ty

Effective Modern C++ 读书笔记 Item 1

最近发现了<Effective Modern C++>这本书,作者正是大名鼎鼎的Scott Meyers——<Effective C++>.<Effective STL>的作者. 而就在C++11逐渐普及,甚至是C++14的新特性也进入大家的视野的时候,<Effective Modern C++>一书应运而生.此书与其前辈一样,通过数十个条款来展开,只不过这次是集中于C++11和C++14的新特性.auto.decltype.move.lambda表达式……

《Effective Modern C++》要点中英文对照

目录 CHAPTER 1 Deducing Types 章节1 类型推导 Item 1:Understand template type deduction. 条款1:理解模板类型推导. Item 2:Understand auto type deduction. 条款2:理解auto类型推导. Item 3:Understand decltype. 条款3:理解decltype. Item 4:Know how to view deduced types. 条款4:知道如何查看推导出来的类型.

决定干点事儿--翻译一下《effective modern c++》

写了很多关于C++11的博客,总是觉得不踏实,很多东西都是东拼西凑.市场上也很少有C++11的优秀书籍,但幸运的是Meyers老爷子并没有闲赋,为我们带来了<effective modern c++>. 我们都要认清,一个人很难超越自我,超越自我的巅峰之作.因为不同的时代,也会早就不同的伟大作品. 说上面这段话的意思就是,我们不能期待<effective modern c++>能达到<effective c++>给我们带来的惊喜,但是也是出自大师之手. Learn ho

《Effective Modern C++》翻译--条款3: 理解decltype

条款3:理解decltype decltype 是一个非常有趣的怪兽.如果提供了一个名字或是表达式,decltype关键字将会告诉你这个名字或这个表达式的类型.通常情况下,结果与你的期望吻合.然而有些时候,decltype产生的结果领你挠头,使你去翻阅参考书或在网上问答中寻求答案. 我们先从通常的情况开始-这里没有暗藏惊喜.联系到在模板类型推导和auto类型推导中发生了什么,decltype关键字就像鹦鹉学舌一样,对于变量名称或表达式类型的推导跟模板类型推导和auto类型推导没有任何变化: co