C++完美转发

完美转发

左值引用和右值引用

左值非临时,具有持续性,可取地址的对象(常量、变量或表达式),例如int a=1,a就为左值

1
2
int a=1;  //a就为左值
++a; //前缀自增/自减表达式的返回值为左值

右值临时的对象,不能取地址,例如int a=1,a+1,则a+1其实就是一个临时生成的对象和值或者1就是右值

1
2
3
int a=1;	//1就为一个右值
a+1; //临时生成,这里的a+1为一个右值
a++; //后缀自增/自减表达式的返回值为右值

左值引用:**对左值的引用就是左值引用(由单个&组成)**,例如int& a,就是一个左值引用

1
2
int a=1;
int &b=a; //int& b就是一个左值引用

右值引用:**对右值的引用就是右值引用(由一对&组成)**,例如int a=1,int&& x=a+1,int&& x就是一个右值引用

1
2
int a=1;
int &&x=a+1; //int &&x就是一个右值引用

注意:

  • 前缀自增/自减表达式的返回值为左值,后缀自增/自减表达式的返回值为右值
  • 常量左值引用是一个可以绑定到任何类型的引用,左值,右值都可以,例如int& a=1
  • 非常量左值引用不能绑定到右值上

万能引用和引用折叠

万能引用:不是一种引用类型,实现了传入左值或左值引用推导出左值引用,传入右值或右值引用推导出右值引用,形式与右值引用一样(一对&)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <type_traits>



template<typename T>
void foo(T&& x){
if(std::is_same_v<T,int&>)
printf("x=%d,是左值引用\n",x);
else if(std::is_same_v<T,int>)
printf("x=%d,是右值引用\n",x);
}

int main (int argc, char *argv[]) {
int a=1;
foo(a);
foo(2);
return 0;
}

//输出结果
x=1,是左值引用
x=2,是右值引用

注意

  1. 只出现在模板类型推导或者auto中
  2. const关键字会剥夺万能引用资格,例如void fun(const T&&)

引用折叠:是C++模板推导的一种规则,当类型出现左值引用折叠到另一个任意类型引用时则折叠成左值引用,当出现右值引用折叠到右值引用时则折叠成右值引用

引用1 引用2 引用拆分 引用折叠
左值引用 左值引用 T& & 左值引用 &
左值引用 右值引用 T& && 左值引用 &
右值引用 左值引用 T&& & 左值引用 &
右值引用 右值引用 T&& && 右值引用 &&

注意:我们并不能在代码中显示写出”引用的引用”这样的代码来表示引用折叠,引用折叠只出现在模板推导中

forward

概念

  • 完美转发是用于泛型编程中,利用引用折叠,将参数类型以及属性性质原封不懂的转发传入到函数中,例如左值,右值,const或者volatile参数是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题
  • 在C++中可以使用std::forward来实现完美转发

函数原型

  • _Tp&& forward<_Tp>(__t)C++实现的完美转发函数,内部实现也是利用了引用折叠的原理实现的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <utility>

    //函数原型

    //针对模板参数除了右值的情况
    template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
    static_assert(!std::is_lvalue_reference<_Tp>::value,
    "std::forward must not be used to convert an rvalue to an lvalue");
    return static_cast<_Tp&&>(__t);
    }


    //针对传入右值,万能引用推导成右值引用,但是进入函数内部变为左值的情况,因为这个时候是例如_Tp=int&&,针对int&& &&折叠后还是int&&,但是右值引用又与左值不匹配导致无法转发
    template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

**自定义实现(引用折叠)**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

template<typename T>
T&& forward(T&& x){
return static_cast<T&&>(x);
}

//如果没有以下这个方法实现,会发现调用forward报错
//是因为在foo中,当传入x为右值,会被推导成右值引用,但是进入函数内部被强转成左值
//而T=int&&,是右值引用,明显跟x左值类型int不对应,所以需要实现下面这部分才行
template<typename T>
T&& forward(T& x){
return static_cast<T&&>(x);
}


void foo1(int& x){
printf("左值引用:x=%d\n",x);
}

void foo1(int&& x){
printf("右值引用:x=%d\n",x);
}
template<typename T>
void foo(T&& x){
foo1(forward<T>(x));
}

int main (int argc, char *argv[]) {
int a=1;
foo(a);
foo(2);
return 0;
}

//输出结果
左值引用:x=1
右值引用:x=2

问题抛出

问题:我有以下代码,当我利用万能引用时正确的推导出了x的类型以及属性,但是x的类型以及属性却不能正确的传入到foo1函数,传入右值后,尽管在模板推导正确成为右值引用,但是进入函数内部时,右值引用变为了左值(因为x作为持续性变量),导致输出结果并不是正确的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

void foo1(int& x){
printf("左值引用:x=%d\n",x);
}

void foo1(int&& x){
printf("右值引用:x=%d\n",x);
}
template<typename T>
void foo(T&& x){
foo1(x);
}

int main (int argc, char *argv[]) {
int a=1;
foo(a);
foo(2);
return 0;
}

//输出结果
左值引用:x=1
左值引用:x=2

利用引用折叠解决

因为模板推导的万能引用的作用导致最终结果只能为左值引用和进入函数内部转为左值的右值引用,因此我们可以利用引用折叠特性,将x强转成T&&类型,当x为左值则会直接转成右值引用,另一情况就是x为左值引用,根据引用折叠依然为左值引用,来实现完美转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

void foo1(int& x){
printf("左值引用:x=%d\n",x);
}

void foo1(int&& x){
printf("右值引用:x=%d\n",x);
}
template<typename T>
void foo(T&& x){
//强转成T&&
foo1(static_cast<T&&>(x));
}

int main (int argc, char *argv[]) {
int a=1;
foo(a);
foo(2);
return 0;
}

//输出结果
左值引用:x=1
右值引用:x=2

使用std::forward解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <utility>

void foo1(int& x){
printf("左值引用:x=%d\n",x);
}

void foo1(int&& x){
printf("右值引用:x=%d\n",x);
}
template<typename T>
void foo(T&& x){
foo1(std::forward<T>(x));
}

int main (int argc, char *argv[]) {
int a=1;
foo(a);
foo(2);
return 0;
}

//输出结果
左值引用:x=1
右值引用:x=2

C++完美转发
https://moonfordream.github.io/posts/C-完美转发/
作者
Moon
发布于
2024年7月25日
许可协议