当前位置: 首页 > article >正文

【C++11】类型分类、引用折叠、完美转发

目录

一、类型分类

二、引用折叠

三、完美转发


一、类型分类

C++11以后,进一步对类型进行了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值
(expiring value,简称xvalue)。

纯右值是指那些字面值常量或求值结果相当于字面值或是一个不具名的临时对象。如: 42、true、nullptr 或者类似str.substr(1, 2)、str1 + str2 传值返回函数调用,或者整形a、b,a++,a+b 等。纯右值和将亡值C++11中提出的,C++11中的纯右值概念划分等价于C++98中的右值。

将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达,如
move(x)、static_cast<X&&>(x)

泛左值(generalized value,简称glvalue),泛左值包含将亡值和左值。

二、引用折叠

C++中不能直接定义引用的引用如int& && r = i; ,这样写会直接报错,通过模板或 typedef中的类型操作可以构成引用的引用。

通过模板或 typedef 中的类型操作可以构成引用的引用时,这时C++11给出了一个引用折叠的规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用。

下面的程序中很好的展示了模板和typedef时构成引用的引用时的引用折叠规则

像f2这样的函数模板中,T&& x参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左值时就是左值引用,传递右值时就是右值引用,有些地方也把这种函数模板的参数叫做万能引用

Function(T&& t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引用折叠规则,就实现了实参是左值,实例化出左值引用版本形参的Function,实参是右值,实例化出右值引用版本形参的Function。

// 由于引用折叠限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x)
{}
// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
template<class T>
void f2(T&& x)
{}
int main()
{
	typedef int& lref;
	typedef int&& rref;
	int n = 0;

    //引用折叠
	lref& r1 = n; // r1 的类型是 int&
	lref&& r2 = n; // r2 的类型是 int&
	rref& r3 = n; // r3 的类型是 int&
	rref&& r4 = 1; // r4 的类型是 int&&

	// 没有折叠->实例化为void f1(int& x)
	f1<int>(n);
	//f1<int>(0); // 报错

	// 折叠->实例化为void f1(int& x)
	f1<int&>(n);
	//f1<int&>(0); // 报错

	// 折叠->实例化为void f1(int& x)
	f1<int&&>(n);
	//f1<int&&>(0); // 报错

	// 折叠->实例化为void f1(const int& x)
	f1<const int&>(n);
	f1<const int&>(0);

	// 折叠->实例化为void f1(const int& x)
	f1<const int&&>(n);
	f1<const int&&>(0);

	// 没有折叠->实例化为void f2(int&& x)
	//f2<int>(n); // 报错
	f2<int>(0);

	// 折叠->实例化为void f2(int& x)
	f2<int&>(n);
	//f2<int&>(0); // 报错

	// 折叠->实例化为void f2(int&& x)
	//f2<int&&>(n); // 报错
	f2<int&&>(0);
	return 0;
}
template<class T>
void Function(T&& t)
{
	int a = 0;
	T x = a;
	//x++;
	cout << &a << endl;
	cout << &x << endl << endl;
}
int main()
{
	// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
	Function(10); // 右值
	int a;

	// a是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
	Function(a); // 左值
	
	// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
	//Function(std::move(a)); // 右值

	const int b = 8;
	// a是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int&t)
	// 所以Function内部会编译报错,x不能++
	Function(b); // const 左值

	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)
	// 所以Function内部会编译报错,x不能++
	//Function(std::move(b)); // const 右值
	return 0;
}

三、完美转发

Function(T&& t)函数模板程序中,传左值实例化以后是左值引用的Function函数,传右值实例化以后是右值引用的Function函数。

结合之前的讲解,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传递给下一层函数Fun,那么匹配的都是左值引用版本的Fun函数这里我们想要保持t对象的属性,就需要使用完美转发实现。

template <class T> T&& forward (typename remove_reference<T>::type&arg);

template <class T> T&& forward (typenameremove_reference<T>::type&& arg);

完美转发forward本质是一个函数模板,他主要还是通过引用折叠的方式实现,下面示例中传递给Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引用返回;传递给Function的实参是左值,T被推导为int&,引用折叠为左值引用,forward内部t被强转为左值引用返回。

template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
{ // forward an lvalue as either an lvalue or an rvalue
	return static_cast<_Ty&&>(_Arg);
}

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<class T>
void Function(T&& t)
{
	Fun(t);

	//Fun(forward<T>(t));
}
int main()
{
	// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
	Function(10); // 右值

	int a;
	// a是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
	Function(a); // 左值

	// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
	Function(std::move(a)); // 右值
	const int b = 8;

	// a是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int&t)
	Function(b); // const 左值

	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)
	Function(std::move(b)); // const 右值
	return 0;
}

看一下第二个使用场景

把上一篇文章最后的代码借过来

int main()
{
	std::list<bit::string> lt;
	bit::string s1("111111111111111111111");

	lt.push_back(s1);
	cout << "*************************" << endl;

	lt.push_back(bit::string("22222222222222222222222222222"));
	cout << "*************************" << endl;

	lt.push_back("3333333333333333333333333333");
	cout << "*************************" << endl;

	lt.push_back(move(s1));
	cout << "*************************" << endl;

	return 0;
}
//运行结果:
string(char* str)
string(const string& s) --拷贝构造
* ************************
string(char* str)
string(string && s) --移动构造
~string() --析构
* ************************
string(char* str)
string(string && s) --移动构造
~string() --析构
* ************************
string(string && s) --移动构造
* ************************
~string() --析构
~string() --析构
~string() --析构
~string() --析构
~string() --析构

本篇完,下篇继续C++11!


http://www.kler.cn/a/459521.html

相关文章:

  • 【嵌入式硬件】直流电机驱动相关
  • seata分布式事务详解(AT)
  • 第二十六天 RNN在NLP中的应用
  • CSS系列(47)-- Animation Timeline详解
  • Vue3 子组件向父组件传递消息(Events)
  • Golang的代码质量分析工具
  • 【数据库初阶】Linux中表的基础操作
  • 【Redis】集群配置(主从复制 哨兵搭建)
  • JPA查询部分字段的最佳实践
  • Python 中的 with open:文件操作的最佳实践
  • 发布远程组件vue2+Webpack和vue3+vite
  • 面试场景题系列:设计云盘系统
  • jmeter设置tps、响应时间监测时间间隔
  • DigitalOcean Kubernetes现已支持VPC natvie集群
  • 【深度学习】Pytorch框架介绍
  • 基于单片机的温湿度采集系统(论文+源码)
  • 整车厂如何规划构建汽车集成安全团队的软件研发能力
  • win10 indy加载ssl 出错could not load ssl library
  • k8s七层代理Ingress(基础知识)
  • C 语言: scanf 函数详解
  • web3基于zkEVM的L2扩容方案-Scroll
  • 华为OD机试真题---服务器广播
  • 活动预告 | Microsoft Azure 在线技术公开课:使用 Azure OpenAI 服务构建生成式应用
  • webrtc音频模块(五) AudioState和AudioDeviceBuffer
  • 服务器nfs文件共享
  • 实验室课程|基于SprinBoot+vue的实验室课程管理系统(源码+数据库+文档)