一、通用引用:
通用引用(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
}

通用引用、引用折叠与完美转发问题相关推荐

  1. C++ 中的万能引用、引用折叠、完美转发

    在学习c++过程中,相信不少同学都或多或少的听过万能引用.引用折叠.完美转发,介绍这几个概念之前,首先列举下左值和右值的概念: 左值和右值: 顾名思义,可以简单的理解为在等号左边的值是左值,再等号右边 ...

  2. [c++]-c++中的左值和右值、左值引用和右值引用、万能引用和引用折叠及完美转发

    1.左值和右值 1.1左值和右值定义 在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置.一般来说,右值是暂时和短暂的,而 ...

  3. C++右值引用与转移和完美转发

    C++右值引用与转移和完美转发 1.右值引用 1.1右值 lvalue 是 loactor value 的缩写,rvalue 是 read value 的缩写 左值是指存储在内存中.有明确存储地址(可 ...

  4. Cpp / 通用引用、引用折叠与完美转发问题

    一.通用引用 通用引用,允许其绑定右值(就像右值引用那样)和左值(就像左值引用那样).而且,它们可以绑定 const 或者非 const 对象,可以绑定 volatile 和非 volatile 对象 ...

  5. Cpp 11 / 万能引用、引用折叠和完美转发

    一.万能引用 1.英文:Universal Reference  . 2.诞生的原因 因为 C++ 中存在左值引用和右值引用,导致若想同时实现既可传入左值又可传入右值的功能,需要对相同函数进行重载,导 ...

  6. 移动语义-右值引用-完美转发-万字长文让你一探究竟

    C++ 右值引用 block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38 ...

  7. 【C++】右值引用、移动语义、完美转发(下篇)

    上篇中,主要讲解了右值引用和移动语义的具体定义和用法.在C++11中几乎所有的容器都实现了移动语义,以方便性能优化.本文以C++11容器中的insert方法为例,详细讲解在容器中移动语义是如何提高性能 ...

  8. C++11新特性之左值右值及移动语句与完美转发

    C左值右值 左值和右值的由来 什么是左值和右值 左值右值的本质 引用 左值引用 右值引用 移动语句与完美转发 移动语句 实现移动构造函数和转移赋值函数 stdmove 完美转发Perfect Forw ...

  9. C++语法学习笔记二十七: 引用折叠,转发、完美转发,forward

    实例代码 // 引用折叠,转发.完美转发,forward#include <iostream>using namespace std;template<typename T> ...

最新文章

  1. HTML的标签描述4
  2. Chrome浏览器查看SSL证书信息
  3. 应届生是这辈子最大的一次优势,也是最后一次!
  4. modbus-rtu qt4-serialport2------micro2440 as host
  5. 什么是迭代器,JS如何实现迭代器
  6. 手把手从python安装到setuptools、pip工具安装
  7. 建立a8 linux开发环境,Fedora 14下建立 omap3530 开发环境 - 交叉编译器
  8. 我是学Java的,为什么要我装JDK
  9. Web前端工作笔记010---IE8兼容_IE8不能使用foreach_indexOf的解决方案
  10. 面向对象的七种设计原则
  11. jQuery Validate 表单验证插件----Validate简介,官方文档,官方下载地址
  12. jquery选择器从认识到使用初级篇
  13. atitit.hbnt orm db 新新增更新最佳实践o7
  14. 嵌入式通过序列号加密总结及flash…
  15. 基于图像特征点匹配的三维立体重建
  16. 单片机实验三(D/A+DMA实验)
  17. 苹果手机iframe撑大父页解决办法
  18. 浅谈阻塞/非阻塞、同步/异步——从linux read()系统调用出发
  19. 互联网最后一个绯闻女友出嫁 大众点评联姻腾讯
  20. 国产化公文管理系统,维护客户信息安全

热门文章

  1. 基于离散余弦变换(DCT)傅里叶变换(DFT)小波变换(DWT)的彩色图像数字水印的嵌入、提取简介及MATLAB实现
  2. 基于Pytorch框架的轻量级卷积神经网络垃圾分类识别系统
  3. this.$emit is not a function
  4. Js迷你图书管理系统
  5. sublime加动画css3,CSS3 Sublime 代码编辑器模拟
  6. 【邮箱】【设置默认抄送邮箱】
  7. 企业邮箱的注册申请,个人企业邮箱申请流程!
  8. VUE组件之数据共享
  9. 北大游记——记一次北大暑期课堂(信息科学营计算机类)
  10. RISC-V学习笔记【中断和异常】