【深入理解C++11】第四章 新手易学,老兵易用

4.2 auto类型推导

auto在C++11中有了新的定义,例如auto a = 1;a的类型将会被推导为int类型,也可以用在例如,将会在4.4节中的追踪返回类型的函数声明,以及7.3节的lambda与auto的配合使用等等。
但是它也有一些使用限制:

  1. 不可以作为函数入参类型。
  2. 不可以非静态成员变量。
  3. 不可以使用auto数组
  4. 不可以作为模板参数
    见以下示例:
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)获取类型时,编译器根据以下四个规则推导:

  1. 如果e是一个没有带括号的标记符表达式,或者类成员访问表达式,那么decltype(e)就是e的实体类型。另外e如果是一个重载函数,将会编译错误。
  2. 否则,假设e是类型T,如果e是一个将亡值(xvalue),那么decltype(e)为T&&。
  3. 否则,假设e是类型T,如果e是一个左值,则decltype(e)为T&。
  4. 否则,假设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(其他元素在线程中删除了)。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值