通用引用、引用折叠与完美转发问题
一、通用引用:
通用引用(universal reference)是Scott Meyers在C++ and Beyond 2012演讲中自创的一个词,用来特指一种引用的类型。这种引用在源代码中(“T&&”)看起来像右值引用,但是它们可以表现左值引用(即“T&”)的行为。它们的双重性质允许它们绑定右值(就像右值引用那样)和左值(就像左值引用那样)。而且,它们可以绑定const或者非const对象,可以绑定volatile和非volatile对象,还可以绑定const和volatile同时作用的对象。它们实际上可以绑定任何东西。构成通用引用有两个条件:
- 必须精确满足T&&这种形式(即使加上const也不行)
- 类型T必须是通过推断得到的(最常见的就是模板函数参数)
(第一个例外)比如将一个左值传递给函数的右值引用参数,且此右值引用指向模板类型参数时,编译器推断,模板类型参数为:实参的左值引用类型
template<typename T> void f(T&&);
int i = 1;
f(i);//编译器推导出T为int&类型
f(10);//推导出T为普通类型int
二、引用折叠:
(第二个例外)通常不能直接定义引用的引用(引用非实体),但是通过类型别名或模板类型参数间接定义是可以的,但这时引用会形成“折叠”,例如上例中,当T被推导为int&
,f的参数类型变为int& &&
(无效代码,用于演示),引用折叠规则:
X& &
,X& &&
,X&& &
折叠为:X&
X&& &&
折叠为:X&&
于是最后我们得到的f的形参类型是int&
类型,即当我们用左值对f进行调用时,实际是用传引用的方式在调用函数。
三、std::move的工作原理:
由以上知识,就可以解释一下std::move的工作原理了:
template<typename T>
typename remove_reference<T>::type&& move(T&& t)
{return static_cast<typename remove_reference<T>::type&&>(t);
}
首先,move函数的参数类型是通用引用T&&
,可以绑定任意类型参数,其次,返回值是remove_reference<T>
的type成员类型的右值引用,比如T被推导为int或者int&
,则remove_reference<int&>::type
为int类型,返回值类型为int&&,最后,函数体中static_cast内的转换过程类似,虽然不能隐式的将一个左值转换为一个右值引用,但是通过static_cast显式转换时允许的(把左值截断问题缩小在使用std::move代码的范围内)。
四、完美转发:
有时候,某些函数需要将其实参连同类型(const、左值、右值等属性)不变的转发给其他函数。
template<typename F,typename T>
void sender(F receiver, T t) //sender函数,接受一个可调用对象和一个模板参数类型的参数
{receiver(t); //sender需要将自己的参数t转发给receiver函数
}
一般情况下这个函数能工作,但是当它调用一个接受引用类型参数的函数时就会有问题:
void rec(int& i) { ++i;}
int j = 1;
rec(j);
cout << j << endl; //输出j为2;//但是通过sender调用时:
template<typename F,typename T>
void sender(F receiver, T t)
{receiver(t);
}
sender(rec, j);
cout << j << endl; //输出j为1
其原因是j传递给sender函数,推断出T为int类型而非引用,j的值是被拷贝到形参t中的,因此对t值的改变不会反应到j中。
这时联想到我们讲的通用引用,将模板参数类型定义为T&&,接受左值时,T会被推断为左值引用类型,经过一次引用折叠,得参数t的类型为左值引用,它对应实参的const属性和左值、右值属性都将得到保持:
template<typename F, typename T>
void sender(F receiver, T&& t) //通用引用
{receiver(t);
}void rec(int& i)
{++i;
}
sender(rec, j);
cout << j << endl; //OK!输出j为2
但是这儿又会遇到另一个问题:
template<typename F, typename T>
void sender(F receiver, T&& t)
{receiver(t);
}void rec(int&& i) //现在rec接受一个右值引用参数
{++i;
}int j = 1;
sender(rec,j);//错误:无法从一个左值实例化int&&
sender(rec,1);//错误:无法从一个左值实例化int&&
当我们试图对一个接受右值引用的函数转发参数时,会报以上错误,不论我们传递给sender函数的是一个左值还是右值。原因是传递给rec函数中形参i的是sender函数中的参数t,函数参数和其他变量一样都是左值表达式!所以会出现将左值绑定到右值引用的错误。
这时需要用到forward函数来保证:当sender函数接受一个右值实参,转发给rec函数时仍然能保持其右值属性。forward函数定义在<utility>
头文件中。
//forward函数
//lvalue (1)
template <typename Type> Type&& forward (typename remove_reference<Type>::type& arg) noexcept
{ return (static_cast<Type&&>(arg));
}
//rvalue (2)
template <typename Type> Type&& forward (typename remove_reference<Type>::type&& arg) noexcept
{ return (static_cast<Type&&>(arg));
}
forward函数的工作原理:由arg接受的实参类型推断出Type类型。
forward函数必须通过显式模板实参来调用,它跟通用引用配合可以保存原始实参的所有特性,回到我们的例子:
template<typename F, typename T>
void sender(F receiver, T&& t)
{receiver(std::forward<T>(t));
}
当传递给sender的是一个右值——比如10时,推断出来的T是一个普通类型即int,传给forward的形参arg的实参就是一个int,从而推出Type是int,这时调用std::forward<int>(t)
返回的是int&&
,保存了原实参的右值属性。
当传递给sender的是一个左值——比如j时,这时我们推断出来的T应该是一个int&了,调用std::forward<int&>(t)
返回int & &&
(无效代码,演示)折叠成为int&,保存了原实参的左值属性。
template<typename F, typename T>
void sender(F receiver, T&& t)
{receiver(std::forward<T>(t));
}void rec1(int i)
{++i;
}
void rec2(int& i)
{++i;
}
void rec3(int&& i)
{++i;
}
int main()
{int i = 1, j = 1, k = 1;/*rec1(i);cout << i << endl; //输出1*//*rec2(j);cout << j << endl; //输出2*//*rec3(std::move(k));cout << k << endl; //输出2*/sender(rec1, i);cout << i << endl;//输出1sender(rec2, j); cout << j << endl;//输出2sender(rec3, std::move(k));cout << k << endl;//输出2
}
通用引用、引用折叠与完美转发问题相关推荐
- C++ 中的万能引用、引用折叠、完美转发
在学习c++过程中,相信不少同学都或多或少的听过万能引用.引用折叠.完美转发,介绍这几个概念之前,首先列举下左值和右值的概念: 左值和右值: 顾名思义,可以简单的理解为在等号左边的值是左值,再等号右边 ...
- [c++]-c++中的左值和右值、左值引用和右值引用、万能引用和引用折叠及完美转发
1.左值和右值 1.1左值和右值定义 在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置.一般来说,右值是暂时和短暂的,而 ...
- C++右值引用与转移和完美转发
C++右值引用与转移和完美转发 1.右值引用 1.1右值 lvalue 是 loactor value 的缩写,rvalue 是 read value 的缩写 左值是指存储在内存中.有明确存储地址(可 ...
- Cpp / 通用引用、引用折叠与完美转发问题
一.通用引用 通用引用,允许其绑定右值(就像右值引用那样)和左值(就像左值引用那样).而且,它们可以绑定 const 或者非 const 对象,可以绑定 volatile 和非 volatile 对象 ...
- Cpp 11 / 万能引用、引用折叠和完美转发
一.万能引用 1.英文:Universal Reference . 2.诞生的原因 因为 C++ 中存在左值引用和右值引用,导致若想同时实现既可传入左值又可传入右值的功能,需要对相同函数进行重载,导 ...
- 移动语义-右值引用-完美转发-万字长文让你一探究竟
C++ 右值引用 block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38 ...
- 【C++】右值引用、移动语义、完美转发(下篇)
上篇中,主要讲解了右值引用和移动语义的具体定义和用法.在C++11中几乎所有的容器都实现了移动语义,以方便性能优化.本文以C++11容器中的insert方法为例,详细讲解在容器中移动语义是如何提高性能 ...
- C++11新特性之左值右值及移动语句与完美转发
C左值右值 左值和右值的由来 什么是左值和右值 左值右值的本质 引用 左值引用 右值引用 移动语句与完美转发 移动语句 实现移动构造函数和转移赋值函数 stdmove 完美转发Perfect Forw ...
- C++语法学习笔记二十七: 引用折叠,转发、完美转发,forward
实例代码 // 引用折叠,转发.完美转发,forward#include <iostream>using namespace std;template<typename T> ...
最新文章
- HTML的标签描述4
- Chrome浏览器查看SSL证书信息
- 应届生是这辈子最大的一次优势,也是最后一次!
- modbus-rtu qt4-serialport2------micro2440 as host
- 什么是迭代器,JS如何实现迭代器
- 手把手从python安装到setuptools、pip工具安装
- 建立a8 linux开发环境,Fedora 14下建立 omap3530 开发环境 - 交叉编译器
- 我是学Java的,为什么要我装JDK
- Web前端工作笔记010---IE8兼容_IE8不能使用foreach_indexOf的解决方案
- 面向对象的七种设计原则
- jQuery Validate 表单验证插件----Validate简介,官方文档,官方下载地址
- jquery选择器从认识到使用初级篇
- atitit.hbnt orm db 新新增更新最佳实践o7
- 嵌入式通过序列号加密总结及flash…
- 基于图像特征点匹配的三维立体重建
- 单片机实验三(D/A+DMA实验)
- 苹果手机iframe撑大父页解决办法
- 浅谈阻塞/非阻塞、同步/异步——从linux read()系统调用出发
- 互联网最后一个绯闻女友出嫁 大众点评联姻腾讯
- 国产化公文管理系统,维护客户信息安全