|
从C++11开始,C++引入了auto关键字,可大大简化代码。
struct WorkeItem
{
int64_t mStartTime;
};
std::set<std::pair<std::string, WorkItem>> workItems;
如上代码,假如我们想要遍历workItems里面每一项,在以前,需要这样写:
std::set<std::pair<std::string, WorkItem>>::iterator it = workItems.begin();
while (it != workeItems.end()) {
xxx
}
可以看到,单单声明一个迭代器,就要写冗长的代码,如果中间有类型写错,编译器的报错信息更是让人头大,从C++11开始,只需要简单如下即可:
auto it = workeItems.begin();
while (it != workItems.end()) {
xxx
}
但是作为一个C++程序员,对于代码的性能、安全性的追求是无止境的,auto声明的变量,到底会被编译器推导成何种类型呢?例如下面的遍历过程中,会有以下几种选择:
auto it = workItems.begin();
while (it != workItems.end()) {
auto curItem = *it; // 会不会产生copy?
auto& curItem = *it; // 到底可以修改吗?
const auto& curItem = *it; // 是不是最安全的做法?
}
WTF,对于不熟悉底层推导机制的人来说,虽然这个关键字节省了大量冗余的编码,但是一不小心可能带来隐藏很深的性能问题。所以我们有必要把这个机制彻底搞清楚
其实C++中的auto关键字的推导规则,与模板类型的推导规则是一样的。我们先来看模板的推导,后面auto自然就理解了,后续的规则介绍我们都以下为例进行介绍:
template<typename T>
void f(ParamType param);
f(expr); // 会根据expr来推导T,以及ParamType的类型
这里注意3个地方:
1. T是模板类型
2. ParamType是参数类型
3. expr是实际代码调用部分给定的类型
情况1:ParamType是引用,但是不是Universal引用
- 如果expr是一个引用,则忽略其引用部分
- 然后根据expr匹配的ParamType类型,来决定T的类型
例如:
template<typename T>
void f(T& param)
现有几个不同的变量
int x1 = 1;
const int x2 = 2;
const int& x3 = x1;
分别进行如下调用:
f(x1); // x1为int
f(x2); // x2为const int
f(x3); // x3为const int&
- 对于x1的调用,我们给的expr为x1,不是引用 ,所以步骤1不生效,expr即为int,编译器会推导出来param的类型为int&,进而根据int&,来得到模板T的类型为int
- 对于x2的调用,我们给的expr为x2,仍然不是引用,所以步骤1仍不生效,expr即为const int,编译器会推导出来param的类型为const int&,进而得到模板T的类型为const int
- 对于x3的调用,我们给的expr为const int&,为引用,所以步骤1生效,去掉引用,即const int,可以看到此时,编译器看到的expr与x2的调用是相同的,因此推导出来的param的类型为const int&, T的类型为const int
可见,其实对于模板函数f,3种不同的调用方式,其实只实例化了两份不同的代码:)
来稍微做一下修改,看如下的例子:
template<typename T>
void f(const T& param);
同样有如下3个不同的变量:
int x1 = 1;
const int x2 = 2;
const int& x3 = 3;
进行如下3次调用:
f(x1);
f(x2);
f(x3);
- 对于x1的调用,expr部分为int,没有引用,于是步骤1不生效,于是param的类型为const int&,进而得出模板T的类型为int
- 对于x2的调用,expr部分为const int,没有引用,于是步骤1不生效,于是param的类型为const int&, 进而得出模板T的类型为int
- 对于x3的调用,expr部分为const int&,有引用,于是去掉引用,按const int推导,即与x2相同,于是param的类型为const int&, 模板T的类型为int
情况2 ParamType是一个Universal引用
记不清什么是Universal Reference的同学,请看上一篇文章,简单来说,Universal Reference就是即可以绑定左值,又可以绑定右值的引用
- 如果expr是一个左值,那么T以及ParamType都会被推导成左值引用
- 如果expr是一个右值,那么按情况1进行推导
这么看起来有后抽象,我们来举具体例子说明
template<typename T>
void f(T&& param);
与之前相同,有3个变量
int x1 = 1;
const int x2 = 2;
const int& x3 = x1;
对应3次调用
f(x1);
f(x2);
f(x3);
- 对于x1,为左值,所以步骤1生效,注意这里是param以及T都会被推导为左值引用,因此param及T的类型都为int &
- 对于x2,为左值,所以步骤1生效,param及T被推导为const int&
- 对于x3,为左值,所以步骤1生效,param及T被推导为const int&
当我们给定下面调用
f(1);
字面量1是右值,所以按情况1推导,首先去掉expr的引用,由于1为字面量,因此此处不生效,所以param的类型为int&&,注意这里的&&代表的是右值引用,类型T为int
情况3 ParamType不是引用
- 如果expr是一个引用,则首先去掉其引用部分
- 如果去掉引用后,expr有const限定符,则将const也去掉
template<typename T>
void f(T param);
仍然举例说明
int x1 = 1;
const int x2 = 2;
const int x3& = x1;
f(x1);
f(x2);
f(x3);
- 对于f(x1),由于x1没有引用,也没有const,于是param的类型为int,模板T的类型也为int
- 对于f(x2),由于x2有const,于是去掉const,所以param的类型为int, 模板T的类型也为int
- 对于f(x3),由于x3既有引用也有const,于是都去掉,所以param的类型为int,模板T的类型也为int
auto类型推导
好了,看完模板类型的推导,我们来到今天的正菜,auto的类型是如何推导的? 其实简单来讲,只要一句话:这两个规则是相同的:)
回想上面的例子:
template<typename T>
void f(ParamType param)
在推导时,auto就相当于T,auto前后增加的限定符,相当于ParamType
对的,就是这么简单,下面让我们再来以实例例子来说明:
auto x1 = 1;
这里,相当于
template<typename T>
void f(T param);
f(x1);
这对应我们上面讲的情形3,1为字面量,类型为int,不是引用,也没有const,于是param的类型被推导成为int,T的类型也为int,所以对于x1来说,其类型为int
const auto x2 = x1;
这里,相当于
template<typename T>
void f(const T param);
f(x)1;
同样,这对应我们上面讲的情形3,x为左值,类型为int,不是引用,也没有const限定符,于是param的类型被推导为const int,T的类型为int,即auto的类型为int,但是对于x2来讲,它是个const int
const auto& x3 = x1;
这里,相当于
template<typename T>
void f(const T& param);
f(x1);
这对应我们上面讲的情形1,x不为引用,因此不用去掉引用限定符,param的类型为const int&,T的类型为int,所以这里auto被推导成为int,x3的类型为const int&
再来看一个情形2的例子:
auto x1 = 1;
auto&& x4 = x1;
这里相当于
template<typename T>
void f(T&& param);
f(x1);
这里命中规则2,ParamType的类型为Universal引用,由于x1的类型为int,为左值,因此ParamType以及T的类型被推导成左值引用,即int&,所以x4的类型为int&
const auto x2 = x1;
auto&& x4 = x2;
x2前面已经分析过,命中规则3,类型为const int,这里相当于
template<typename T>
void f(T&& param);
f(x2);
这里命中规则2,由于x2是左值,所以ParamType以及T被推导成const int&,即x4的类型为const int&
auto&& x5 = 1;
这里相当于
template<typename T>
void f(T&& param);
f(1);
这里命中规则2,1为字面量,右值,int&&(注意这里是右值引用),转而到规则1的情形,首先去掉引用,变成int,所以ParamType被推导成int&&,T的类型为int,即auto换成int,所以x5的类型为int&&,即为一个右值引用
怎么样,是不是其实很简单,聪明的你明白了吗,来检验一下自己,开篇的问题是否可以回答了?
auto it = workItems.begin();
while (it != workItems.end()) {
auto curItem = *it; // 会不会产生copy?
auto& curItem = *it; // 到底可以修改吗?
const auto& curItem = *it; // 是不是最安全的做法?
} |
|