Effective Modern C++:02auto
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的更多相关文章
- Effective Modern C++:04智能指针
裸指针有着诸多缺点:裸指针的声明中看不出它指向的是单个对象还是数组:裸指针的声明中也无法看出使用完它指向的对象后是否需要删除,也就是声明中看不出裸指针是否拥有其指向的对象:即使知道要析构裸指针指向的对 ...
- Effective Modern C++:03转向现代C++
07:在创建对象时注意区分()和{} 自C++11以来,指定初始化值的的方式包括使用小括号,等号,以及大括号: ); // initializer is in parentheses ; // ini ...
- Effective Modern C++:01类型推导
C++的官方钦定版本,都是以ISO标准被接受的年份命名,分别是C++98,C++03,C++11,C++14,C++17,C++20等.C++11及其后续版本统称为Modern C++. C++11之 ...
- Effective Modern C++:08调整
41:针对可复制的形参,在移动成本低且一定会被复制的前提下,考虑将其按值传递 class Widget { public: void addName(const std::string& ne ...
- Effective Modern C++:07并发API
C++11的志伟功勋之一,就是将并发融入了语言和库中,因此在C++的历史上,程序员可以首次跨越所有平台撰写具有标准行为的多线程程序. 35:优先选用基于任务而非基于线程的程序设计 如果需要以异步的方式 ...
- Effective Modern C++:06lambda表达式
lambda表达式实际上是语法糖,任何lambda表达式能做到的,手动都能做到,无非是多打几个字.但是lambda作为一种创建函数对象的手段,实在太过方便,自从有了lambda表达式,使用复杂谓词来调 ...
- Effective Modern C++:05右值引用、移动语义和完美转发
移动语义使得编译器得以使用成本较低的移动操作,来代替成本较高的复制操作:完美转发使得人们可以撰写接收任意实参的函数模板,并将其转发到目标函数,目标函数会接收到与转发函数所接收到的完全相同的实参.右值引 ...
- Effective Modern C++翻译(1):序言
/*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...
- [C++11] Effective Modern C++ 读书笔记
本文记录了我读Effective Modern C++时自己的一些理解和心得. item1:模板类型推导 1)reference属性不能通过传值参数传入模板函数.这就意味着如果模板函数需要一个refe ...
随机推荐
- redis教程(三)-----redis缓存雪崩、缓存穿透、缓存预热
缓存雪崩 概念 缓存雪崩是由于原有缓存失效(过期),新缓存未到期间.所有请求都去查询数据库,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机.从而形成一系列连锁反应,造成整个系统崩溃. 解决 ...
- .h头文件 .lib动态链接库文件 .dll 动态链接库
(1).h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的. 附加依赖项的是.lib 不是.dll 若生成了DLL ,则肯定也生成 LIB文件 如果要完成源代码的编译和链接,有头文件和 ...
- flex 手册摘要
个人学习 摘抄翻译 http://www.cs.princeton.edu/~appel/modern/c/software/flex/flex.html#SEC1 程序的格式 分成显示的三部分 由% ...
- Laravel使用EasyWechat 进行微信支付
微信支付和EasyWeChat这个包都是巨坑, 文档写的稀烂, 记录下防止以后又重复踩坑: 安装教程在这: https://www.jianshu.com/p/82d688e1fd2a
- useradd -M -s /sbin/nologin mysql -g mysql 报错 Creating mailbox file
由于之前使用以下命令删除了mysql账户 userdel mysql groupdel mysql #如果删除了mysql用户,对应的组也会被删除(只有一个用户的情况下) 执行以下命令时报错 ...
- 使用Python Requests上传表单数据和文件
在Python环境下写一个HTTP客户端,发送POST请求,同时上传表单数据和文件,我们可以使用Requests模块来实现.代码如下: data = { 'name': 'nginx' } files ...
- HDU6200 mustedge mustedge mustedge
不用看题就知道这是和什么tarjan.缩点或桥一类有关的题. 谁让他取题目叫一个mustedge还连续写3次的(哦,似乎是因为那个比赛的题目都是这个画风) 必须的边 >必须要经过的边 > ...
- vue下使用nginx刷新页面404
nginx 是一个代理的服务器.出现的问题:写好的页面通过nginx作为代理的服务器给别的同事看的时候发现了新写的页面打开就404,并且从其他页面跳转可以看到但是刷新页面就404.解决方法:在文件中的 ...
- jeecg流程梳理学习
jeecg 流程梳理 角色admin 管理员 fgld学校分管领导 bgs学校办公室 xbld系部领导 xbky系部科员jxky bmld部门领导 发文申请applyUserIdadmin${assi ...
- opencv java swing 图片灰度化 二值化
工程下载地址 https://download.csdn.net/download/qq_16596909/11503860 基于maven 首先引入opencv <!-- https://mv ...