深入理C++11
4.2 auto类型推导
auto在C++11中有了新的定义,例如auto a = 1;
a的类型将会被推导为int类型,也可以用在例如,将会在4.4节中的追踪返回类型的函数声明,以及7.3节的lambda与auto的配合使用等等。
但是它也有一些使用限制:
- 不可以作为函数入参类型。
- 不可以非静态成员变量。
- 不可以使用auto数组
- 不可以作为模板参数
见以下示例:
void func(auto x = 1) {} //1. 无法通过编译
struct str {
auto var = 0; //2. 非静态成员,无法通过编译
};
char x[3];
auto z[3] = x; //3. auto数组,无法通过编译
vector<auto> v = { 1 }; //4. auto模板参数,无法通过编译
4.3 decltype
4.3.1 typeid与decltype
在C++98中对动态类型支持就是C++中的运行时类型识别(RTTI)。RTTI机制是为每一个类型产生一个type_info类型的数据,程序员可以在程序中随时查询一个变量的类型。C++11提供了hash_code以方便运行时作比较。
//C++11新增hash_code
cout << typeid(Node).hash_code() << endl; //打印一堆数字
float a;
double b;
//声明c的类型
decltype(a + b) c;
cout << typeid(c).name() << endl; //打印:double
我们使用的std::is_same
模板函数的成员类型value,是在编译器得到类型信息,不同的是hash_code是运行时得到的信息。
除了typeid以外,RTTI也包括了dynamic_cast等特性。要记得RTTI确实会带来一些开销。
4.3.2 decltype的应用
一些简单的使用如下:
vector<int> vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin(); i < vec.end(); ++i) {}
template<typename T1, typename T2>
void sum(T1 &t1, T2 &t2, decltype(t1 + t2) &s)
{
s = t1 + t2;
}
一个来自标准库的典型的例子是基于decltype的模板result_of
,作用是推导函数的返回类型。
namespace gxlemon
{
template<typename>
struct result_of;
template<typename F, typename ...Args>
struct result_of<F(Args...)>
{
typedef decltype(std::declval<F>()(std::declval<Args>()...)) type;
};
}
//使用如下
typedef double(*pFunc)();
//要求模板参数 是一种类型,而不是具体的某个对象
//这样a就是该函数类型的返回类型
gxlemon::result_of<pFunc()>::type a;
4.3.3 decltype推导四规则
int i;
decltype(i) a;
decltype((i)) b; //“b”: 必须初始化引用
通过decltype(e)获取类型时,编译器根据以下四个规则推导:
- 如果e是一个没有带括号的标记符表达式,或者类成员访问表达式,那么decltype(e)就是e的实体类型。另外e如果是一个重载函数,将会编译错误。
- 否则,假设e是类型T,如果e是一个将亡值(xvalue),那么decltype(e)为T&&。
- 否则,假设e是类型T,如果e是一个左值,则decltype(e)为T&。
- 否则,假设e是类型T,则decltype(e)为T。
简单举例解释一下标记符表达式int arr[4]
,arr就是一个标记符表达式,例如arr[3] + 0, arr[3],都不是标记符表达式。
推导示例如下(书籍代码清单4-25):
注意左值引用总是需要初始化的。C++11标准库提供了is_lvalue_reference
模板类来帮助判断一个推导结果。
示例见书籍代码清单4-26
4.3.4 cv限制符的继承与冗余的符号
与auto类型推导时不能带走cv限制符不同,decltype是能“带走”表达式的cv限制符的。如果对象的定义中含有const或者volatile则使用decltype进行推导时其成员不会继承const或volatile限制符,看一下示例:
const int ic = 0;
volatile int iv;
struct S { int i; };
const S a = { 0 };
volatile S b;
volatile S *p = &b;
cout << "is_const<decltype(ic)>::value " << is_const<decltype(ic)>::value << endl;
cout << "is_volatile<decltype(iv)>::value "<< is_volatile<decltype(iv)>::value << endl;
cout << "is_const<decltype(a)>::value " << is_const<decltype(a)>::value << endl;
cout << "is_volatile<decltype(b)>::value " << is_volatile<decltype(b)>::value << endl;
cout << "is_const<decltype(a.i)>::value " << is_const<decltype(a.i)>::value << endl;
cout << "is_volatile<decltype(b.i)>::value " << is_volatile<decltype(b.i)>::value << endl;
与auto相同,decltype从表达式推导出类型后,进行类型定义时,也会允许一些冗余符号,比如cv限制符和引用符号&。
4.4 追踪返回类型
4.4.1 追踪返回类型的引入
直接看一个例子,我们需要一个泛型的sum函数,入参我们可以通过模板实现,但是返回值就不太好处理了,所以我们需要一个可以承载任何类型的返回值。
//C++编译器规则:变量使用前必须声明,所以可以追踪返回类型,见下例子
template<typename T1, typename T2>
decltype(t1 + t2) sum1(T1 t1, T2 t2)
{
return t1 + t2;
}
template<typename T1, typename T2>
auto sum2(T1 t1, T2 t2) -> decltype(t1 + t2)
{
return t1 + t2;
}
4.4.2 使用追踪返回类型的函数
看一个题目:
//可能是一个面试题
int(*(*pf())())()
{
return nullptr;
}
// -> 返回一个a函数的指针的函数 -> 返回一个函数指针a
auto pf1() -> auto (*)() -> int(*)()
{
return nullptr;
}
可以看到我们可以使用返回类型跟踪很方便的声明该类型,以下见详细代码:
//声明函数指针类型
typedef int(*PFUNC)();
//一个函数 返回int
int fun()
{
return 1;
}
//返回一个函数指针
PFUNC funRetPF()
{
return fun;
}
//声明返回一个函数指针的类型
typedef int(*(*PFUNCRETFUN)())();
//可能是一个面试题
int(*(*pf())())()
{
PFUNCRETFUN f = funRetPF;
return f;
}
// -> 返回一个a函数的指针的函数 -> 返回一个函数指针a
auto pf1() -> auto (*)() -> int(*)()
{
PFUNCRETFUN f = funRetPF;
return f;
}
cout << std::is_same<decltype(pf1), decltype(pf)>::value << endl;
cout << "pf1: " << pf1()()() << endl;
cout << "pf : " << pf()()() << endl;
4.5 基于范围的for循环
只需要注意,迭代for循环时,源迭代对象不能发生变化,例如删除对象等操作,以下是一个示例:
std::vector<int> gVecs;
void fun()
{
for (int i = 0; i < 100; ++i)
{
gVecs.pop_back();
}
}
int main()
{
for (int i = 0; i < 200; ++i)
{
gVecs.push_back(i);
}
thread thd(fun);
//迭代
for (int val : gVecs)
{
if (val == 50) //触发线程删除部分元素
{
thd.detach();
}
cout << val << " ";
}
getchar();
return 0;
}
运行后发现崩溃,可以发现迭代对象的end已经超过当前容器的大小100(其他元素在线程中删除了)。