05:优先使用auto,而非显示类型声明

显示类型声明有下面一些缺点:

int x; //未初始化,或者初始化为0,视语境而定

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

}
}

另外,如果想要使用闭包的类型来声明变量,但是闭包的类型只有编译器知道。

有了auto之后,上面这些缺点都可以解决:

int x1; // 潜在的未初始化风险
auto x2; // 编译错误!必须初始化
auto x3 = ; // fine template<typename It> // as before
void dwim(It b, It e) {
while (b != e) {
auto currValue = *b;

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

使用auto声明变量,必须初始化。

或许你认为没必要使用auto来声明变量持有闭包,使用std::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; };

抛开词法上的啰嗦不谈,使用std::function和auto还是有所不同。使用auto声明的变量,类型与闭包一致,从而其要求的内存量和闭包也一样;使用std::function声明的变量,不管给定的签名如何,其都占有固定尺寸的内存,而这个尺寸对于其存储的闭包而言不一定够用,这种情况下,std::function就会使用堆内存来存储闭包。因此,std::function对象一般都会比auto声明的变量使用更多的内存;而且std::funciton的实现细节一般会限制内联,并产生间接函数调用,因此,通过std::function来调用闭包几乎必然会比通过auto声明的变量调用同一闭包来的要慢。所以,这种情况下std::funciton比auto又大又慢。

下面的代码:

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

标准规定v.size()的返回值类型是std::vector<int>::size_type,它是一个无符号整数,因此很多人就直接写成上面那样了。在32位Windows上,unsigned和std::vector<int>::size_type的内存尺寸是一样的,但是到了64位Windows上,unsigned是32位,而std::vector<int>::size_type则是64位,这就有可能导致异常行为。使用auto就不会有这样的麻烦:auto sz = v.size();

下面的代码:

std::unordered_map<std::string, int> m;
for (const std::pair<std::string, int>& p : m) {
… // do something with p
}

上面的代码看似合理,但是,std::unordered_map的value_type实际类型是std::pair<const Key, T>(std::map也一样),因此m中的std::pair的类型实际上是std::pair<const std::string, int>,但是上面的循环中,p的类型不是这样的,因此编译器就需要把m中的每个对象做一次复制操作,形成一个p可以绑定的临时对象,在循环的每次迭代结束时再析构该临时对象。使用auto就不会有这样的麻烦:

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

这样使用auto不仅效率更高,而且打字也更少;犹有进者,如果对p取地址,则取得的一定是m中某个元素的地址,而不使用auto的版本,取得的则是临时对象的地址,而且该临时对象在循环迭代结束时会被析构。

最后,auto类型可以随着其初始化表达式的类型变化而自动变化,这意味着使用auto,某些重构动作就顺手做掉了,比如某个函数之前返回int,后来又觉得long更合适一些,如果函数返回结果存储在auto声明的变量中,就无需在调整变量的类型了。

06:当auto推导出非预期类型时应当使用显式的类型初始化

某些情况下,auto的类型推导会和你想的南辕北辙。举一个例子,下面的features函数接受一个Widget,返回一个std::vector<bool>,其中每个bool标识Widget是否支持某种特性:

std::vector<bool> features(const Widget& w);

Widget w;
bool highPriority = features(w)[]; // is w high priority?
processWidget(w, highPriority); // process w in accord with its priority

这份代码没有任何问题。但是如果我们改用auto:

auto highPriority = features(w)[]; // is w high priority?
processWidget(w, highPriority); // undefined behavior!

代码虽然可以编译,但是调用processWidget却是未定义行为。这是因为这种情况下,highPriority的类型已经不是        bool 了。尽管std::vector<bool>是bool的容器,但是对std::vector<bool>的operator[]操作,并不是返回容器中元素的引用(std::vector::operator[]对所有类型都返回引用,除了bool)。事实上,它返回的是一个std::vector<bool>::reference对象(一个在std::vector<bool>中内嵌的class)。

之所以要弄出一个std::vector<bool>::reference,是因为std::vector<bool>做过特化,用一种压缩形式表示其持有的bool元素,每个bool元素用一个bit来表示。因为std::vector<T>的operator[]应该返回一个T&,但是C++禁止bits的引用。没办法返回一个bool&,因此std::vector<T>的operator[]就返回一个行为上和bool&相似的对象std::vector<bool>::reference,该对象在任何bool适用的场合都表现的和bool一样,它通过隐式转换成bool来实现这一点。所以,下面的代码:

bool highPriority = features(w)[];

std::vector<bool>::reference隐式转换成了bool,以便能初始化highPriority,因而highPriority便持有了std::vector<bool>中第5位的值。

但是换成auto之后,highPriority的类型就成了std::vector<bool>::reference类型。而highPriority的值也视std::vector<bool>::reference的实现而定。std::vector<bool>::reference的实现方式可能是内部包含一个指针,指针指向的内存包含相应的位信息。这种情况下,features函数返回了一个临时的std::vector<bool>,暂时称其为temp,在temp上执行operator[]操作,返回的std::vector<bool>::reference中包含一个指针指向temp的内部数据,赋值给highPriority后,highPriority也持有一个指针指向相同的地址。但是在表达式的最后,临时对象temp被销毁,highPriority内部的指针就成了悬空空悬指针。从而导致调用processWidget成了一种未定义行为。

实际上,std::vector<bool>::reference仅仅是代理类的一个例子而已,一个通用的法则就是,“隐形”代理类不能和auto和平共处,因为代理类对象的生命周期一般设计为不会超过单条语句,所以要避免下面这种代码形式:

auto someVar = expression of "invisible" proxy class type;

如何知道代理类的存在呢?一般情况下,可以在库文档中找到代理类的说明,文档不够用时,也可以去看头文件。比如,std::vector<bool>::operator[]的代码如下:

namespace std { // from C++ Standards
template <class Allocator>
class vector<bool, Allocator> {
public:

class reference { … };
reference operator[](size_type n);

};
}

一旦 auto 被推导为代理类的类型而不是它被代理的类型时就有可能出现问题,auto本身没有问题。解决方案是强制进行类型转换,我把这种方法叫做显式的类型初始化原则。

显式类型初始化原则依然使用auto声明变量,但是要对初始化表达式进行强制类型转换。比如:

auto highPriority = static_cast<bool>(features(w)[]);

这里, features(w)[5] 还是返回一个 std::vector<bool>::reference 的对象,但是强制类型转换将表达式的类型转换成了bool,从而auto将highPriority推导为bool。

这种用法不限于会产生代理类对象的初始化物。它同样可以应用于你想要强调你意在创建一个类型有别于初始化表达式类型的变量的场合,比如下面的代码:

double calcEpsilon();
float ep = calcEpsilon(); // impliclitly convert double to float

尽管calcEpsilon返回double,但是你认为float的精度已经够用了,所以你用一个float变量ep存储返回值。如果使用auto,则可以:

auto ep = static_cast<float>(calcEpsilon());

Effective Modern C++:02auto的更多相关文章

  1. Effective Modern C++:04智能指针

    裸指针有着诸多缺点:裸指针的声明中看不出它指向的是单个对象还是数组:裸指针的声明中也无法看出使用完它指向的对象后是否需要删除,也就是声明中看不出裸指针是否拥有其指向的对象:即使知道要析构裸指针指向的对 ...

  2. Effective Modern C++:03转向现代C++

    07:在创建对象时注意区分()和{} 自C++11以来,指定初始化值的的方式包括使用小括号,等号,以及大括号: ); // initializer is in parentheses ; // ini ...

  3. Effective Modern C++:01类型推导

    C++的官方钦定版本,都是以ISO标准被接受的年份命名,分别是C++98,C++03,C++11,C++14,C++17,C++20等.C++11及其后续版本统称为Modern C++. C++11之 ...

  4. Effective Modern C++:08调整

    41:针对可复制的形参,在移动成本低且一定会被复制的前提下,考虑将其按值传递 class Widget { public: void addName(const std::string& ne ...

  5. Effective Modern C++:07并发API

    C++11的志伟功勋之一,就是将并发融入了语言和库中,因此在C++的历史上,程序员可以首次跨越所有平台撰写具有标准行为的多线程程序. 35:优先选用基于任务而非基于线程的程序设计 如果需要以异步的方式 ...

  6. Effective Modern C++:06lambda表达式

    lambda表达式实际上是语法糖,任何lambda表达式能做到的,手动都能做到,无非是多打几个字.但是lambda作为一种创建函数对象的手段,实在太过方便,自从有了lambda表达式,使用复杂谓词来调 ...

  7. Effective Modern C++:05右值引用、移动语义和完美转发

    移动语义使得编译器得以使用成本较低的移动操作,来代替成本较高的复制操作:完美转发使得人们可以撰写接收任意实参的函数模板,并将其转发到目标函数,目标函数会接收到与转发函数所接收到的完全相同的实参.右值引 ...

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

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

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

    本文记录了我读Effective Modern C++时自己的一些理解和心得. item1:模板类型推导 1)reference属性不能通过传值参数传入模板函数.这就意味着如果模板函数需要一个refe ...

随机推荐

  1. 编码格式简介(ANSI、GBK、GB2312、UTF-8、UTF-16、GB18030和 UNICODE)

    很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物,他们把这称为”字节”.再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态 ...

  2. 【agc019f】AtCoder Grand Contest 019 F - Yes or No

    题意 有n个问题答案为YES,m个问题答案为NO. 你只知道剩下的问题的答案分布情况. 问回答完N+M个问题,最优策略下的期望正确数. 解法 首先确定最优策略, 对于\(n<m\)的情况,肯定回 ...

  3. Python学习之for循环--输出1-100中的偶数和登录身份认证

    输出1-100中的偶数 效果图: 实现代码: for i in range(2,101,2): print(i,end = '\t') if(i == 34): print('\n') if (i = ...

  4. jmeter参数化之配置元件CSV控件

    1.     用badboby进行录制,录制完成后保存,用JMeter格式进行保存,如:登陆.jmx 2.     在jmeter中打开保存的文件登陆.jmx. 3.     对登陆账号和密码进行参数 ...

  5. 转载:腾讯与新浪的通过IP地址获取当前地理位置(省份)的接口

    腾讯的接口是 ,返回数组 http://fw.qq.com/ipaddress 返回值 var IPData = new Array("61.135.152.194"," ...

  6. Redis源码解析:25集群(一)握手、心跳消息以及下线检测

    Redis集群是Redis提供的分布式数据库方案,通过分片来进行数据共享,并提供复制和故障转移功能. 一:初始化 1:数据结构 在源码中,通过server.cluster记录整个集群当前的状态,比如集 ...

  7. agc033

    A题意:给你个黑白矩阵,每次黑的周围一圈会变黑,求多少次之后全黑.n <= 1000 解:BFS即可. #include <bits/stdc++.h> ; , , -, }; , ...

  8. c++设计模式:策略模式

    1.主要思想:例如针对不同的算法,创建不同的类. #include <iostream> using namespace std; // The abstract strategy cla ...

  9. PHP学习1.5-预定义超全局数组变量

    1.PHP 预定义的超全局变量数组 特性: a.特殊的数组,操作方式没有区别 b.不用声明,php脚本中默认存在,因为在php中不用定义,所以在自定义变量是应避免和预定的全局变量同名 c.在全局范围内 ...

  10. 本周汇总 动态rem适配移动端/块状元素居中/透明度

    1.动态rem适配移动端 !function(){ var width = document.documentElement.clientWidth; var head=document.getEle ...