社区首页 >专栏 >C++中的万能引用和完美转发

C++中的万能引用和完美转发

发布于 2019-01-25 16:46:05
3.9K2
举报
文章被收录于专栏:给永远比拿愉快

C++中的万能引用和完美转发

  1. 阅读这篇博文需要了解C++中的左值(lvalue)和右值(rvalue)的概念,详情参见我的另外一篇博文:C++移动语义及拷贝优化
  2. 万能引用和完美转发多涉及到模板的使用,如若不是自己写模板,则可不用关心

万能引用(Universal Reference)

首先,我们来看一个例子:

代码语言:javascript
复制
#include <iostream>

using std::cout;
using std::endl;


template<typename T>
void func(T& param) {
    cout << param << endl;
}


int main() {
    int num = 2019;
    func(num);
    return 0;
}

这样例子的编译输出都没有什么问题,但是如果我们修改成下面的调用方式呢?

代码语言:javascript
复制
int main() {
    func(2019);
    return 0;
}

则会得到一个大大的编译错误。因为上面的模板函数只能接受左值或者左值引用(左值一般是有名字的变量,可以取到地址的),我们当然可以重载一个接受右值的模板函数,如下也可以达到效果。

代码语言:javascript
复制
template<typename T>
void func(T& param) {
    cout << "传入的是左值" << endl;
}
template<typename T>
void func(T&& param) {
    cout << "传入的是右值" << endl;
}



int main() {
    int num = 2019;
    func(num);
    func(2019);
    return 0;
}

输出结果:

代码语言:javascript
复制
传入的是左值
传入的是右值

第一次函数调用的是左值得版本,第二次函数调用的是右值版本。但是,有没有办法只写一个模板函数即可以接收左值又可以接收右值呢?

C++ 11中有万能引用(Universal Reference)的概念:使用T&&类型的形参既能绑定右值,又能绑定左值。

但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。

所以,上面的案例我们可以修改为:

代码语言:javascript
复制
template<typename T>
void func(T&& param) {
    cout << param << endl;
}


int main() {
    int num = 2019;
    func(num);
    func(2019);
    return 0;
}

引用折叠(Universal Collapse)

万能引用说完了,接着来聊引用折叠(Univers Collapse),因为完美转发(Perfect Forwarding)的概念涉及引用折叠。一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:

  • 左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
  • 左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
  • 右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
  • 右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用

但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:

所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用

即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。

完美转发(Perfect Forwarding)

下面接着说完美转发(Perfect Forwarding),首先,看一个例子:

代码语言:javascript
复制
#include <iostream>

using std::cout;
using std::endl;

template<typename T>
void func(T& param) {
    cout << "传入的是左值" << endl;
}
template<typename T>
void func(T&& param) {
    cout << "传入的是右值" << endl;
}


template<typename T>
void warp(T&& param) {
    func(param);
}


int main() {
    int num = 2019;
    warp(num);
    warp(2019);
    return 0;
}

猜一下,上面的输出结果是什么?

代码语言:javascript
复制
传入的是左值
传入的是左值

是不是和我们预期的不一样,下面我们来分析一下原因:

warp()函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个warp()函数调用实参是左值,所以,warp()函数中调用func()中传入的参数也应该是左值;第二个warp()函数调用实参是右值,根据上面所说的引用折叠规则,warp()函数接收的参数类型是右值引用,那么为什么却调用了调用func()的左值版本了呢?这是因为在warp()函数内部,左值引用类型变为了右值,因为参数有了名称,我们也通过变量名取得变量地址。

那么问题来了,怎么保持函数调用过程中,变量类型的不变呢?这就是我们所谓的“完美转发”技术,在C++11中通过std::forward()函数来实现。我们修改我们的warp()函数如下:

代码语言:javascript
复制
template<typename T>
void warp(T&& param) {
    func(std::forward<T>(param));
}

则可以输出预期的结果。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年01月21日,如有侵权请联系 cloudcommunity@tencent.com 删除
评论
登录后参与评论
2 条评论
热度
最新
template<typename T>void func(T &¶m) 形参也是万能引用,编译会报错 error: call of overloaded ‘func(int&)’ is ambiguous,应该改成 (const T &¶m)
回复点赞举报
“这是因为在warp()函数内部,左值引用类型变为了右值,因为参数有了名称” 这里应该表述有误吧,应是右值引用变成了左值。
回复点赞举报
推荐阅读
编辑精选文章
换一批
【Modern Cpp】从万能引用到完美转发
但凡阅读过源码,就知道STL里面充斥着大量的T&&以及std::forward,如果对这俩特性或者原理不甚了解,那么对源码的了解将不会很彻底,或者说是一知半解。之所以这么说,是因为当初吃过这个亏,在研究某个特性的时候,仅仅关注大体逻辑,而这种阅读方式往往忽略了某些非常重要的细节,以为自己了解了整个原理,结果往往就是这种被忽略的细节导致了线上故障(详见之前文章P1级故障,年终奖不保)。所以,今天借助本文,聊聊STL中两个常见的特性万能引用 和 完美转发,相信读完本文后,对这俩特性会有一个彻底的了解,然后嘴里不自觉吐出俩字:就这?😁
高性能架构探索
2023/06/13
5050
现代C++之万能引用、完美转发、引用折叠(万字长文)
0.导语1.问题引入2.引入万能引用3.万能引用出现场合4.理解左值与右值4.1 精简版4.2 完整版4.3 生命周期延长4.4 生命周期延长应用5.区分万能引用6.表达式的左右值性与类型无关7.引用折叠和完美转发7.1 引用折叠之本质细节7.2 示例与使用7.3 std::move()与std::forward()源码剖析8.不要返回本地变量的引用9.总结10.补充
公众号guangcity
2019/12/30
6.8K0
现代C++之万能引用、完美转发、引用折叠(万字长文)
C++ 左值和右值
在C++11之前,一个变量分为左值和右值:左值是可以放在=运算符左边的值,有名字,可以用&运算符取地址(如 int n = 10;n即为左值);右值则是只能放在=运算符右边,没有名字,不能用&运算符取地址的值,一般是临时变量(非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b的返回值)、lambda表达式、不跟对象关联的字面量值,例如true,100等。
永远的Alan
2023/04/15
1.2K0
终于弄明白了万能引用和右值引用的区别
1,移动语义:使用移动操作替换复制操作,比如移动构造函数和移动赋值运算符替换复制构造函数和复制赋值运算符
用户9831583
2022/12/04
1.9K0
【C++11特性篇】一文带小白轻松理解【万能引用(引用折叠)】&【完美转发】
YY的秘密代码小屋
2024/01/23
1.9K0
【C++11特性篇】一文带小白轻松理解【万能引用(引用折叠)】&【完美转发】
【C++11】万能引用与完美转发
这里有4个函数,我们很容易能看出来它们是一个重载的关系 然后我们给这样一个函数模板
YIN_尹
2024/01/23
2020
【C++11】万能引用与完美转发
C++11移动语义与右值引用
C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力。如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能。参考如下程序:
恋喵大鲤鱼
2019/02/22
1.1K0
[Effective Modern C++(11&14)]Chapter 5: Rvalue References, Move Semantics, PF
1. 理解std::move和std::forward 从std::move和std::forward不能做的地方开始入手是有帮助的,std::move不会移动任何值,std::forward也不会转发任何东西,在运行时,他们不会产生可执行代码,一个字节也不会:)。他们实际上是执行转换的函数模板。std::move无条件的把它的参数转换成一个右值,而std::forward在特定条件下将参数转换成右值。 //c++11中std::move的简化版本 template<typename T> typename
昊楠Hacking
2018/05/26
5.1K0
深入理解C++中的move和forward!
导语 |  在C++11标准之前,C++中默认的传值类型均为Copy语义,即:不论是指针类型还是值类型,都将会在进行函数调用时被完整的复制一份!对于非指针而言,开销极其巨大!因此在C++11以后,引入了右值和Move语义,极大地提高了效率。本文介绍了在此场景下两个常用的标准库函数:move和forward。 一、特性背景 (一)Copy语义简述 C++中默认为Copy语义,因此存在大量开销。 以下面的代码为例: 0_copy_semantics.cc #
腾讯云开发者
2022/08/26
2K0
深入理解C++中的move和forward!
【重学C++】05 | 说透右值引用、移动语义、完美转发(下)
大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第五讲,在第四讲《【重学C++】04 | 说透右值引用、移动语义、完美转发(上)》中,我们解释了右值和右值引用的相关概念,并介绍了C++的移动语义以及如何通过右值引用实现移动语义。今天,我们聊聊右值引用的另一大作用 -- 完美转发。
会玩code
2023/07/08
3000
【笔记】C++2.0新特性
本篇是这段时间看的侯捷关于C++的课程《C++2.0新特性》的笔记,课程内容大家自己找吧。这个课程主要是我用来回顾C++11的特性和拾遗的,因此笔记中只记录了我认为课程中比较重要的内容。这门课的很多内容都来自《C++标准库》和《Modern Effective C++》,在看了在看了。
ZifengHuang
2022/03/04
9090
【笔记】C++2.0新特性
【专业技术】从4行代码看右值引用
概述   右值引用的概念有些读者可能会感到陌生,其实他和C++98/03中的左值引用有些类似,例如,c++98/03中的左值引用是这样的: int i = 0;int& j = i;   这里的int&是对左值进行绑定(但是int&却不能绑定右值),相应的,对右值进行绑定的引用就是右值引用,他的语法是这样的A&&,通过双引号来表示绑定类型为A的右值。通过&&我们就可以很方便的绑定右值了,比如我们可以这样绑定一个右值: int&& i = 0;   这里我们绑定了一个右值0,关于右值的概念会在后面介绍。右值引
程序员互动联盟
2018/03/16
1.6K0
【专业技术】从4行代码看右值引用
C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』
自从C++98以来,C++11无疑是一个相当成功的版本更新。它引入了许多重要的语言特性和标准库增强,为C++编程带来了重大的改进和便利。C++11的发布标志着C++语言的现代化和进步,为程序员提供了更多工具和选项来编写高效、可维护和现代的代码
北 海
2023/11/17
5170
C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』
C++右值引用/移动语义
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,也可以出现在赋值符号的右边。定义时const修饰符后的左 值,不能给它赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
二肥是只大懒蓝猫
2023/03/30
4790
C++右值引用/移动语义
【C++11】 改成程序性能的方法--完美转发
所谓的完美转发,实际上就是指在C++函数模板中,完全按照函数模板的参数类型将参数传递给函数模板中调用的另外一个参数。通俗点解释就是,如果一个参数不管是右值引用还是左值引用,函数调用时都不会改变参数的类型。C++11给我们提供了这样一个函数std::forward,它就是专门为完美转发而生的,实际使用时它会完全按照参数本来的类型进行转发,而不是改变。
CPP开发前沿
2021/11/16
2690
【笔记】《C++Primer》—— 第16章:模板与泛型编程
这一章介绍了面向对象编程中最复杂的部分:模板与模板编程,读起来很吃力,总结也写了很久。其中16.2的类型转换部分会有点绕,16.4的可变参数模板则很实用,可以有效提高我们的开发效率。这篇内容较多较难,可以的话应该仔细看书慢慢看。
ZifengHuang
2020/07/29
1.5K0
【笔记】《C++Primer》—— 第16章:模板与泛型编程
【C++修炼之路】27.右值引用
以下所要讲到的,以及右值引用的都是为了提高性能,这是其他语言所不具备的,而本文章就围绕了大量的场景将右值引用的细节分割并逐个击破。
每天都要进步呀
2023/03/28
2690
【C++修炼之路】27.右值引用
C++ Trick:右值引用、万能引用傻傻分不清楚
C++11标准颁布距今已经十年了,在这个标准中引入了很多新的语言特性,在进一步强化C++的同时,也劝退了很多人,其中就包含右值引用。
果冻虾仁
2022/03/22
7380
【C++】右值引用(极详细版)
在讲右值引用之前,我们要了解什么是右值?那提到右值,就会想到左值,那左值又是什么呢?
The sky
2023/04/28
2.1K0
【C++】右值引用(极详细版)
左值引用与右值引用
(左值)引用作为指针的非完全替代品,不仅降低了用户的编写难度,又由于其直接作为别名的特点,不用申请新空间去保存由于赋值、函数返回等引起的不必要的拷贝中产生的临时变量,而提升了效率。
比特大冒险
2023/05/09
4400
目录
  • C++中的万能引用和完美转发
    • 万能引用(Universal Reference)
      • 引用折叠(Universal Collapse)
        • 完美转发(Perfect Forwarding)
        领券