深入完美转发

本文最后更新于:1 年前

深入探讨右值引用应用中的完美转发

1.为什么使用完美转发

我们先看以下的案例:

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
class CMyClass
{
public:
CMyClass() {};
~CMyClass() {};
int m_1 = 1;
protected:
private:

};
void Code_1(int t)
{
_tprintf(_T("%d"),t);
}

void Code_1(CMyClass t)
{
_tprintf(_T("%d"),t.m_1);
}

template<typename T>
void Forward_1(T t)
{
Code_1(t);
}

int _tmain()
{
CMyClass Object;
Forward_1(Object); //左值
Forward_1(CMyClass()); //右值
return 0;
}

我们的Forward_1函数使用值传递参数,会有以下问题:

  • 值参数会产生额外临时对象拷贝

所以我们考虑使用引用传递,因为引用传递有以下特性:

  • 传入引用类型不会有额外的内存开销

所以我们实现以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Code_2(int& t)
{
_tprintf(_T("%d"),t);
}

void Code_2(CMyClass& t)
{
_tprintf(_T("%d"),t.m_1);
}

template<typename T>
void Forward_2(T& t)
{
Code_2(t);
}

int _tmain()
{
CMyClass Object;
Forward_2(Object); //左值
Forward_2(CMyClass()); //右值
return 0;
}

但又出现了一个问题:

  • 如何区分左值和右值?

这就涉及到完美转发,即让一个函数既可以接收左值,又可以接收右值

2.函数重载实现完美转发

我们对引用传递参数的代码进行修改,使其可以初步实现完美转发,主要使用了常函数重载:

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
void Code_2(int& t)
{
_tprintf(_T("Left"));
}
void Code_2(const int& t)
{
_tprintf(_T("Right"))
}
void Code_2(CMyClass& t)
{
_tprintf(_T("Left"));
}
void Code_2(const CMyClass& t)
{
_tprintf(_T("Right"));
}
template<typename T>
void Forward_1(T& t)
{
Code_2(t);
}

int _tmain()
{
CMyClass Object;
Forward_1(Object); //左值
Forward_1(CMyClass()); //右值
return 0;
}

在未出现右值引用这一C++新特性前,常函数重载是实现完美转发的惯用方式

但本文主要介绍使用右值引用实现完美转发的方式,因为常函数重载具有以下缺点:

  • 编写大量的重载函数模板 造成代码冗余 因为各个模板都需要重载
  • 无法实现移动语义 因为常函数内部无法对成员进行修改 不能move

为了解决以上问题,引入右值引用的方法

3.右值引用实现完美转发

在函数模板中使用右值引用的语法定义参数被解释为可以接收右值,也可以接收左值此时被成为“万能引用”

1
2
3
4
5
template<typename T>
void Forward_2(T&& t)
{
Code_2(t);
}

但这是还存在问题:无论传入的形参时左值还是右值,对于模板来说,形参都是可以寻址的,因此都是一个左值

解决方法:

1
2
3
4
5
6
7
8
9
10
11
template<typename T>
void Forward_2(T&& t)
{
Code_2(forward<T>(t)); //对形参进行强制类型转换 当时左值时还是左值,右值还是右值域,使用的主要方法是引用折叠,如下
}

Forward_3(Object); //左值还是左值
//Forward_3(T&& &) == Forward_3(T&) //引用折叠

Forward_3(CMyClass()); //右值还是右值
//

右值引用的好处 是可以对成员变量进行一个写操作 相较于常引用的优点 但常引用既可以绑定左值 又可以绑定右值

此时Code_2代码需要重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Code_2(int& t)
{
_tprintf(_T("Left"));
}
void Code_2(int&& t)
{
_tprintf(_T("Right"));
}

void Code_2(CMyClass& t)
{
_tprintf(_T("Left"));
}
void Code_2(CMyClass&& t)
{
_tprintf(_T("Right"));
}

深入完美转发
https://wlpswmt.github.io/2023/03/21/深入完美转发/
作者
Sivan Zhang
发布于
2023年3月21日
许可协议