在概念上说,auto关键字和它看起来一样简单,但是事实上,它要更微妙一些的。使用auto会让你在声明变量时省略掉类型,同时也会防止了手动类型声明带来的正确性和性能上的困扰;虽然按照语言预先定义的规则,一些auto类型推导的结果,在程序员的视角来看却是难以接受的,在这种情况下,知道auto是如何推导答案便是非常重要的事情,因为在所有可选方法中,你可能会回归到手动的类型声明上(because falling back on manual type declarations is an alternative that’s often best avoided.)。

这一章包括了auto的细则

 

条款5:auto比显示的类型声明要更好

这是一个看起来非常简单的例子

int x;

等一下,该死,我忘记初始化x了,所以它的值是不确定的,也许它被初始化为0了,不过这要取决于它的上下文,哎…

没有关系,接下来,在下面这个例子中我们通过解引用一个迭代器来初始化一个变量currValue

template<typename It> //函数的名字是dwim ("do what I mean")
void dwim(It b, It e) // 对于所有b到a
{ // 范围内的元素
while (b != e) {
typename std::iterator_traits<It>::value_type
currValue = *b;

} }

恩,通过typename std::iterator_traits<It>::value_type来表达一个迭代器所指的变量的类型?哦,我之前说过C++很有趣吗,我真的说过吗?

 

现在让我们声明一个局部变量,这个变量的类型是一个闭关的类型,但是这个闭包的类型只有编译器才能知道,你可以写出吗?

该死,用C++进行编程一点都不像我想象的那么有趣。

的确,在之前,它就是这样,但是当C++11给auto引入了新的用法,所有的这些问题都不见了,使用auto声明的变量必须通过对应的初始化式子来推导出自己的类型,所以他们必须要被初始化的,这意味着你可以站在现代C++的火车上对过去的哪些因为忘记初始化变量而导致的问题说再见了。

int x1; // 有潜在的未初始化风险

auto x2; // error!需要初始化

auto x3 = 0; //很好,x3的值已经被初始化

 

你同样可以通过使用auto来声明一个局部变量,用解引用一个迭代器来初始化对应的值。

template<typename It> // 和之前一样

void dwim(It b, It e)
{
while (b != e) {
auto currValue = *b;

}
}

因为auto使用了类型推导规则(参见条款2),它可以表示一些只有编译器知道的类型

auto derefUPLess = // 一个比较函数.
[](const std::unique_ptr<Widget>& p1, //std::uniquer_ptr
const std::unique_ptr<Widget>& p2) //的widget
{ return *p1 < *p2; }; //对象

很酷吧,在C++14中,auto又前进了一步,因为lambda表达式的参数也可以使用auto了

auto derefLess =      // C++14中
[](const auto& p1, //的比较函数
const auto& p2) // 可以比较任何
{ 
    return *p1 < *p2; //指针指向的 
};                    // 东西
 

也行你认为我们不需要使用auto来声明持有一个闭包(holds a closure)的变量,因为我们可以使用std::function对象来完成同样的操作,是的,的确可以,但,恩,你也许要问了,std::function这又是什么东西?

 

std::function是C++11标准库中用来模拟函数指针的东西,函数指针只能指向函数,类似的std::function只能指向任何的可调用对象(callable object),可调用对象是被认为可以像函数一样的东西,就像你声明函数指针的时候,你必须标注出函数的类型,当你声明std::function的时候,你也必须通过模板的参数标注出函数的类型,例如你可以声明一个叫func的std::function对象,它可以指向以下函数类型的可调用对象:

bool(const std::unique_ptr<Widget>&, // C++11中比较
const std::unique_ptr<Widget>&) // std::unique_ptr<Widget>
//的函数原型

完整的结果是这样的:

std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)> func;

 

因为lambda表达式产生的可调用对象,闭包也可以通过std::function对象表示,这意味着我们可以声明新的版本的derefUPLess而不需要使用auto关键字

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; };

认识到我们在声明时需要重复复杂的函数类型这一点是很重要的,同时使用std::function的对象和使用auto声明的对象并不完全一样,一个使用auto声明的变量持有闭包的类型和这个闭包一样,并且需要的空间也一样,而用std::function声明的变量持有的闭包是std::function模板的一个实例,对任何的给定的函数原型,所需要的内存大小都是一样的,如果分配的大小不足,std::function会分配一些堆上的空间来进行储存,这导致了使用std::function声明的对象比起auto声明的对象通常需要更多的内存,并且实现上的细节限制了内敛函数的使用,通过std::function调用一个闭包也更慢一些,简而言之就是std::function比auto声明的闭包要更大,更慢一些,加上它可能产生内存不足的异常,你也看到了上面的那个例子,使用auto会比使用std::function达成同样的效果轻松很多,所以在使用auto还是std::function声明一个闭包的较量中,auto获胜了(一个类似的参数可以通过auto或者std::function来产生,持有std::bind的调用结果,但是根据条款34,,我会尽我最大的努力来让你使用lambdas替代std::bind)(

A similar argument can be

made for auto over std::function for holding the result of calls to std::bind, but

in Item 34, I do my best to convince you to use lambdas instead of std::bind, anyway.

)

auto的优势除了避免忘记初始化变量,冗长的变量声明式,持有闭包的能力之外,另一个可以避免的问题是类型截断,这是一个你之前可能见到过的例子。

std::vector<int> v;

unsigned sz = v.size();

v.size()的返回类型是std::vector<int>::size_type,但是很少会有程序员意识到std::vector<int> ::size被指定为一个无符号整形了,很多程序员认为unsigned就可以了,写出了如上的程序代码,这可能会引起很多有意思的后续反应,在32位windows机器上,unsigned和std::vector<int>::size_type的类型是一样的,但是在64位机器上,unsigned是32位,然而std::vector<int>::size_type是64位,这意味你的程序可能在32位机器上表现正常,而在64位机器上表现的不正确,同时我们都不想在程序移植上花费太多的时间。

使用auto可以避免这个问题

auto sz = v.size(); // sz的类型是std::vector<int>::size_type

依然无法想象到使用auto带来的好处?不妨看看下面的代码

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

for (const std::pair<std::string, int>& p : m)
{
… // 对p进行处理
}

这看起来非常的合理吧,但是这里是有一个问题的,你看到了吗?

错误之处在于std::unorder_map的key是const,所以std::pair的类型不应是std::pair<std:string,int>,应该是std::pair<const std:string,int>,但是上面代码对p类型的不是这个,所以编译器试图找到一个方式将std::pair<const std::string,int>对象转换为std::pair<std::string,int>对象,他们会对m的每一个元素创建一个临时对象,然后将p绑定到这个临时对象上,在每一次循环结束的时候,这个临时变量会被摧毁。所以如果你写出了这样的循环,你会对程序的行为表示惊讶,因为你的意图肯定是想将一个p的引用绑定到m的每一个元素上。

使用auto可以避免这样意料之外的事情。

for (const auto& p : m)
{
… // 和之前一样
}

这不仅仅更有效率,同时也简化了很多代码,此外,它还有一个非常有吸引力的特性就是如果你想获得p地址,你肯定能够获得一个指向m中元素的指针,而在不使用auto的版本中,你会获得一个临时对象,在每一次循环结束时都会被摧毁。

最后的两个例子,当应该使用std::vector<int>::size_type时使用了unsigned和应该使用std::pair<const std::string,int>时使用了std::pair<std::string,int>,证明了显示的类型声明有时候会导致一些你不希望的隐式的类型转换,而如果你使用auto声明目标变量,你就不必担心想要声明的变量和对应的初始化式间的类型不匹配问题了。

所以使用auto而不是显示的类型声明就有很多的理由了,是的,auto也并不完美,auto声明的变量的类型会从相应的初始化式中推导出来,一些推导的结果可能不是你所期待或想要的,在某些情况下,你需要了解条款2和6中讨论过的问题,所以我不会在这个再强调这个问题了,相反,我会把我的精力转到另一个方面,auto代码具有更好的可读性。

 

先放松一下吧,auto也只是可选的,并不是强制的,如果在你的判断中,使用显示的类型声明会让你的代码更整洁,并且更容易可维护的话,你可以继续使用它,但是要记住,C++并没有创造出一个新的东西,这个东西在编程界已经存在了,被叫做类型推导,一些其他的静态类型的语言(比如C#,D,Scala,Visual Basic)也或多或少有了类似的特性,更不用说那些静态类型的函数式语言了(比如ML,Haskell,OCaml,F#)在某种程度上,这是由于动态声明的语言例如Perl,python或者是Ruby,这些语言几乎不使用显示的类型声明的广泛发展,软件社区在类型推导上有了很多的经验,证明这些技术可以被应用在传统的大型的可创造新,可维护性的优质的代码库上。

 

有些开发者可能认为使用auto时,会让你难以在第一时间看出变量的类型是什么,然而IDE本身显示变量类型的能力可以减轻这个问题(可以参考条款4中讨论的IDE展示问题),而且在很多情况下抽象的变量类型会和精确的类型一样有效,例如,只是为了知道一个对象是容器,计数器,智能指针,而不关注这个容器,计算器或者智能指针的精确类型是什么,此外如果你的变量的名字起的足够好的话,知道变量的抽象类型是件很容易的事情。

 

事实是显示的类型声明会引入一些微小的错误,此外使用auto初始化的变量的类型会随着初始化式类型的变化自动发生变化,这同时意味着在代码利用auto,会让重构变的简单,例如,如果一个函数最初的返回值是int,但是后来你觉得long更好,如果你使用auto储存函数的返回类型的话,代码会自动下一次编译的时候自动更新,但是你使用了显示的类型声明int,你可能需要修改每一个函数调用的地方。

 

请记住

  • 1、使用auto声明的变量必须被初始化,这不会导致类型不匹配照成的可移植性和效率问题,可以减轻重构的过程,并且通常比显示的类型声明需要更少的代码。
  • 2、auto使用的一些陷阱在条款2和条款6中描述了。

Effective Modern C++翻译(6)-条款5:auto比显示的类型声明要更好的更多相关文章

  1. Effective Modern C++翻译(7)-条款6:当auto推导出意外的类型时,使用显式的类型初始化语义

    条款6:当auto推导出意外的类型时,使用显式的类型初始化语义 条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zi ...

  2. Effective Modern C++翻译(3)-条款2:明白auto类型推导

    条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...

  3. Effective Modern C++翻译(4)-条款3:了解decltype

    条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结 ...

  4. Effective Modern C++翻译(2)-条款1:明白模板类型推导

    第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和dec ...

  5. Effective Modern C++翻译(5)-条款4:了解如何观察推导出的类型

    条款4:了解如何观察推导出的类型 那些想要知道编译器推导出的类型的人通常分为两种,第一种是实用主义者,他们的动力通常来自于软件产生的问题(例如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能 ...

  6. Effective Modern C++翻译(1):序言

    /*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...

  7. [Effective Modern C++] Item 5. Prefer auto to explicit type declarations - 相对显式类型声明,更倾向使用auto

    条款5 相对显式类型声明,更倾向使用auto 基础知识 auto能大大方便变量的定义,可以表示仅由编译器知道的类型. template<typename It> void dwim(It ...

  8. item 5: 比起显式的类型声明,更偏爱auto

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 啊,简单愉快的代码: int x; 等等,讨厌!我忘了初始化x,所 ...

  9. [Effective Modern C++] Item 1. Understand template type deduction - 了解模板类型推断

    条款一 了解模板类型推断 基本情况 首先定义函数模板和函数调用的形式如下,在编译期间,编译器推断T和ParamType的类型,两者基本不相同,因为ParamType常常包含const.引用等修饰符 t ...

随机推荐

  1. C++实现tar包解析

    tar(tape archive)是Unix和类Unix系统上文件打包工具,可以将多个文件合并为一个文件,使用tar工具打出来的包称为tar包.一般打包后的文件名后缀为".tar" ...

  2. windows7 64位机上安装配置CUDA7.5(或8.0)+cudnn5.0操作步骤

    按照官网文档 http://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html#axzz4TpI4c8v ...

  3. Object C学习笔记4-内存管理

    Object-C的内存管理和.NET有些不一样,.NET的内存回收机制是使用GC自动处理回收,而Object-C本质上还是C语言,所以很多时候还是需要手动去管理内存回收. 1. Object-C生成一 ...

  4. UWP 记一次WTS 和 UCT翻车经历

    这次翻车,真的,在网上绝对找不到回答的. 只有在WTS的Issues讨论中才找到,哈哈 不过这个应该比较少遇到吧,据我所知,提出Issue那个大胸弟和我都遇到了... 翻车具备的条件如下: 1. 使用 ...

  5. 稳重商务风格教师求职简历免费word模板

    30款稳重商务风格教师求职简历免费word模板,也可用于其他专业和职业,个人免费简历模板,个人简历表免费,个人简历表格. 声明:该简历模板仅用于个人欣赏使用,请勿用于商业用途,谢谢. 下载地址:百度网 ...

  6. C++将一个vector中的内容复制到另一个vector结尾

    在使用vector容器的时候,需要将一个vector中的内容复制到另一个vector结尾,如何实现呢? 使用vector的insert方法 template <class InputIterat ...

  7. Python里的类和对象简介

    ---恢复内容开始--- Python里的类  对象=属性+方法: 对象的属性主要是指主要的特征和参量,而方法主要是指函数: 类是一个具有一定特征和方法的集合,而对象是类的一个:类和对象的关系就如同模 ...

  8. 一个IT男的表白

    致BCD6 CEC0 C3F4 转一轮肩胛骨 倒一杯铁观音 白驹过隙,倏忽两秋 远方有希望和梦想 有火车.微信美颜视频聊天和碧根果 有你的支持 如果身旁没有你 生活无趣失去动力 就像python失去类 ...

  9. 腾讯hr面

    腾讯hr面面经 20181018 寒暄几句 hr自我介绍 hr介绍面试和最后出结果的时间周期 进入主题 自我介绍 考研成绩专业第一 聊考研(考研的经过.考研和保研) 本科成绩 考研成绩超长发挥还是正常 ...

  10. MAC node + git + bower 简单安装

    一 node 安装 打开https://nodejs.org/en/ nodejs官网 下载安装文件 双击.pkg 文件 自动安装即可 二 安装git 打开 http://code.google.co ...