本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!

博客已经迁移到这里啦

啊,简单愉快的代码:

int x;

等等,讨厌!我忘了初始化x,所以它的值是不确定的。可能,它可能被初始化成了0,这取决于你的编译环境。哎。

不要紧,让我们简单并愉快地声明一个局部变量,通过解引用一个iterator来初始化它:

template<typename It>
void dwim(It b, It e)
{
while(b != e){
typename std::iterator_traits<It>::value_type
currValue = *b;
...
}
}

呸,真的要用“typename std::iterator_traits::value_type”表示iterator指向的值的类型吗?我已经忘了这有多愉快了,等等,我之前真的说过这愉快吗?

好的,再看一个简单愉快的例子(第三个了):愉快地声明一个局部变量,让他的类型是一个闭包。噢,对的,闭包的类型只有编译器知道,因此不能被写出来,哎,讨厌。

讨厌,讨厌,讨厌!C++编程,并不是一段愉快的经历(它本应该是愉快的)。

好的吧,它曾经不是。但是在C++11中,由于auto提供的好意,所有的这些麻烦都离去了。auto变量从初始化表达式中推导它们的类型,所以它们必须被初始化。这意味着,当你快速行驶在现代C++的超级高速公路上时,你能和变量未初始化问题挥手再见了:

int x1;			//可能未初始化

auto x2;		//错误!需要初始化表达式

auto x3 = 0;	//好的,x的值是良好定义的

通过对iterator解引用来声明局部变量时,这个高速公路没有之前那样的困难:

template<typename It>	//和之前一样
void dwim(It b, It e)
{
while(b != e){
auto currValue =*b;
...
}
}

并且,因为auto使用类型推导(看 Item 2),它能表示只有编译器知道的类型:

auto derefUPLess =
[](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };

非常酷,在C++14中,温度进一步下降(事情变得更简单),因为lambda表达式的参数可以涉及auto:

auto derefLess =
[](const auto& p1,
(const auto& p2)
{ return *p1 < *p2; };

虽然很酷,你可能觉得我们不需要使用auto来声明一个变量来包含闭包,因为我们可以使用std::function对象。这是对的,我们能这么做,但是事情并不是你想的那样。有的读者可能在想“什么是std::function对象” ,那就让我们先来理清这个对象把。

std::function是C++11标准库中的模板,这个模板扩张了函数指针的概念。函数指针只能指向函数,但是,std::function对象能指向所有可调用对象。也就是,所有能像函数一样用“()”调用的东西。就像你创建函数指针的时候,必须明确指向函数的类型(也就是你想指向的函数的签名),当你传入std::function对象时,你必须明确你引用的函数的类型。你通过std::function模板的参数来做到这一点。举个例子,为了声明一个能调用任何下面函数签名的std::function对象,

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

你会这么写:

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

因为lambda表达式产生一个可调用对象,所以闭包能被存放在std::function对象中。这意味着我们能不使用auto就声明一个C++11版本的derefUpLess

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的构造函数将申请堆内存来存储闭包。结果就是相比auto声明的对象,std::function对象通常要使用更多的内存。再考虑下实现的细节,由于inline函数的限制,间接函数的调用,调用闭包时,通过std::function对象调用总是比通过auto声明的对象调用要慢。换句话说,std::function方法通常比auto方法更大更慢,并且可能产生内存溢出异常。更好的是,就像你在上面的例子中看到的那样,写”auto“要做的工作完全少于写std::function实例类型。在auto和存储闭包的std::function对比中,auto完胜了。(相似的争论会出现在std::bind函数的返回值的存储中,同样可以使用auto或std::function,但是在item 34中,我尽全力来让你信服,无论如何使用lambdas表达式来代替std::bind)

auto的优点不局限于避免未初始化的变量,冗长的变量声明和直接闭包的声明。它的另外一个能力就是能避免我们由于使用“类型快捷方式”(“type shortcuts”)而造成问题。这里给出一些你看起来会做的事,你可能会这么写:

std::vector<int> v;
...
unsigned sz = v.size();

v.size()的官方返回类型是std::vector::size_type,但是很少有开发者意识到这点。std::vector::size_type常被指定为无符号整形,所以很多开发者认为unsigned够用了,并写代码时也会像上面这么写。这会造成一些有趣的结果。举个例子,在32位的Windows下,unsigned和std::vector::size_type大小是一样的。但是64位Windows下,unsigned是32位的,而std::vector::size_type是64位的。这意味着工作在32位Windows和工作在64位Windows下会表现的不一样,当把你的程序从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::unordered_map的键的部分是const的,所以在hash table(std::unordered_map的存储类型)中的std::pair的类型不是std::pair<std::string, int>,它是std::pair<const std::string, int>(译注:

//这是stl源码书中看到的hash_map的类型定义, 可以看到
//它的类型是pair<const Key, T>
typedef hashtable<pair<const Key, T>, Key, HashFcn,
select1st<pair<const Key, T>, EqualKey, Alloc> ht;

)。

但是这不是上面循环中的变量p的声明类型。对于这个结果,编译器将努力去找到一个方法来转换std::pair<const std::string, int>对象(也就是hash table的内容)到std::pair<std::string, int>对象(p声明的类型)。通过拷贝m里的对象,创造一个个临时对象,然后把p引用绑定到临时对象上去,p就成功地被创造出来了。在每一次循环结尾,临时对象将被销毁。如果你写下这样的循环,你会被它的效率所震惊,因为大概你想做的只是简单地把p的引用绑定到m的每个元素p上。

这样无意识的类型不匹配能通过auto来消除:

for(const auto& p : m)
{
... //as before
}

这不仅更加有效率,也更容易写。更好的是,这个代码有一个非常吸引人的特征,那就是如果你获取p的地址,你肯定能取到一个指向存在于m中的p的指针,但是如果不使用auto,你会取到一个指向临时变量的指针,而且这个临时变量在每一次循环结尾会销毁。

最后的两个例子---当你应该写std::vector::size_type时却写了unsigned和当你应该写std::pair<const std::string, int>时却写了std::pair<std::string, int>---演示了写下明确的类型如何导致隐式的转换,并且这种转换是你不想要的。如果你使用auto来作为目标变量的类型,你不需要考虑声明的变量和用来初始化的表达式之间的类型不匹配。

因此,这里有很多原因来让你选择auto而不是显式的类型声明。到目前为止,auto不是完美的。对于每个auto变量,类型是从它的初始化表达式推导来的,并且一些初始化表达式拥有的类型不是我们能预料到以及想要的。会出现这样情况的情景,以及你要如何做已经在item 2item 6中讨论了,所以我在这不会涉及他们了。作为替换,我将把我的注意力放在在你使用传统的类型声明时,你使用auto会有什么不同之处:生成的源代码的可读性。

首先,做一下深呼吸并且放轻松。auto是可选的,不是必需品。如果经过你的专业判断,使用明确的类型声明能使你的代码更清晰,可维护性更好,或其他好处,你可以继续自由地使用明明确的类型声明。但是把这记在心里,在编程语言世界中众所周知的是,C++采取的类型推导不是新的东西。其他静态类型的语言(比如C#,D,Scala,Visual Basis)或多或少也有这样的特性,更不用说那些静态类型的函数式语言(比如ML,Haskell,OCaml,F#,等等)。这部分,由于动态语言(比如Perl,Python,和Ruby)的成功,类型很少被显式地写出来。软件开发社区对类型推导有很深的经验,并且也证明了这技术可以用在创造和维护大型的,企业级的代码中。

由于auto的使用,在源文件中,只通过看一眼无法明确对象的类型,一些开发者就感到困扰了。然而,IDE有显示这些对象类型的能力,让这个问题缓和了一些(可以参考item 4中说的IDE类型显示的问题),并且,在很多情况下,一个比较抽象的对象类型和明确的对象类型是一样有用的。举个例子,你不需要知道具体的类型,只要知道一个对象是一个容器,或一个计数器,或一个智能指针,这常常就够了。假设使用良好的名字,这些抽象类型的信息就已经能在名字中得知了。

事实是,不管是在正确性上还是效率上,写出明确的类型常常起到的作用很小,却造成更多的问题。更重要的是,auto类型的变量能在初始化的表达式改变时自动改变,这意味着使用auto能简化重构。举个例子,如果一个函数的返回类型声明为了int,但是之后你觉得使用long会更好,在你下次编译程序的时候,如果你把调用这个函数的结果存在auto类型变量中,auto类型变量会自动改变自己的类型。如果结果被存放在明确声明的类型中,你就需要找到所有调用这个函数的地方并且手动修改它们。

你要记住的事
  • 比起显式类型声明,auto变量必须被初始化,它常常免于由类型不匹配造成的可移植性和效率的问题,它能简化程序的重构,并且通常只需要写的更少的代码。

  • auto类型变量的陷阱在Item 2item 6中讨论。

item 5: 比起显式的类型声明,更偏爱auto的更多相关文章

  1. [Effective Modern C++] Item 6. Use the explicitly typed initializer idiom when auto deduces undesired types - 当推断意外类型时使用显式的类型初始化语句

    条款6 当推断意外类型时使用显式的类型初始化语句 基础知识 当使用std::vector<bool>的时候,类型推断会出现问题: std::vector<bool> featu ...

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

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

  3. Kotlin函数使用综述与显式返回类型分析

    位置参数与具名参数: 继续接着上一次https://www.cnblogs.com/webor2006/p/11498842.html的方法参数学习,再定义一个函数来说明具名参数的问题: 调用一下,先 ...

  4. Effective Modern C++ ——条款5 优先选择auto,而非显式型别声明

    条款5 对于auto ,他的好处不仅仅是少打一些字这么简单. 首先在声明的时候, 使用auto会让我们养成初始化的习惯: auto x;//编译不通过必须初始化. 再次对于auto而言,它可以让我们定 ...

  5. item 6: 当auto推导出一个不想要的类型时,使用显式类型初始化的语法

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 Item 5解释了比起显式指定类型,使用auto来 ...

  6. Spring装配bean(在java中进行显式配置)

    1.简单介绍 Spring提供了三种装配机制: 1.在XML中进行显式配置: 2.在java中进行显式配置: 3.隐式的bean发现机制和自动装配. 其中,1和3项在项目中经常使用,而在java中进行 ...

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

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

  8. C++中的显式类型转化

    类型转化也许大家并不陌生,int i; float j; j = (float)i; i = (int)j; 像这样的显式转化其实很常见,强制类型转换可能会丢失部分数据,所以如果不加(int)做强制转 ...

  9. 分析器错误消息: 类型“test.test.testx”不明确: 它可能来自程序集“F:\testProject\bin\test.test.DLL”或程序集“F:\testProject\bin \testProject.DLL”。请在类型名称中显式指定程序集。

    问题描述: RT 分析器错误消息: 类型“test.test.testx”不明确: 它可能来自程序集“F:\testProject\bin\test.test.DLL”或程序集“F:\testProj ...

随机推荐

  1. Spring Boot 技术总结

    Spring Boot(一):入门篇 Spring Boot(二):Web 综合开发 Spring Boot(三):Spring Boot 中 Redis 的使用 Spring Boot(四):Thy ...

  2. 【锁】Oracle死锁(DeadLock)的分类及其模拟

    [锁]Oracle死锁(DeadLock)的分类及其模拟 1  BLOG文档结构图 2  前言部分 2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不 ...

  3. HDU ACM 1224 Free DIY Tour (SPFA)

    Free DIY Tour Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  4. Centos7系统详细的启动流程

    熟悉系统启动流程对于我们学习Linux系统是非常有帮助的,虽然基础,但能帮助我们更加理解Linux系统的工作机制.以下将以CentOS发行版为例来介绍Linux系统的启动流程,因为在CentOS 5. ...

  5. PHP APP端支付宝支付

    应业务需求,做了支付宝支付和微信支付,今天分享一下手机端app支付宝支付对接流程,实际开发过程是前后端分离,前端调用后端API接口,实现功能返回数据,我所用的跨挤啊为TP5,大致可以分为四步: 1.在 ...

  6. Windows 下配置 Apache 支持 https

    1.打开cmd ,输入  F: // 切换到Apache安装路径,我的Apache安装目录在 F盘 2.cd F:\Apache\bin 3.set "openssl_conf = F:\A ...

  7. Alpha冲刺!Day13 - 小结

    Alpha冲刺!Day13 - 小结 各个成员今日完成的任务 今天团队极限编程12小时,从早上九点要求每个人给出一张电脑全屏截图以示开始干活,每两小时汇报进度确认已经做了什么.现在在做什么. 各节点列 ...

  8. arcgis如何求两个栅格数据集的差集

    栅格数据集没有擦除功能,现在有栅格A和栅格B,怎么求两个栅格的差集C 具体步骤如下: 1.首先利用栅格计算器,把栅格B中的value全部赋值为0 输入语句:"栅格B" * 0 2  ...

  9. jsp 一点点

    jsp学习 jsp -处理 作为正常的页面,你的浏览器发送一个http请求道web服务器. web 服务器承认一个JSP页面的HTTP请求,并将其转发给一个JSP引擎. JSP引擎从磁盘加载JSP页面 ...

  10. 关于RBAC(Role-Base Access Control)的理解(转)

    基于角色的访问控制(Role-Base Access Control) 有两种正在实践中使用的RBAC访问控制方式:隐式(模糊)的方式和显示(明确)的方式. 今天依旧有大量的软件应用是使用隐式的访问控 ...