Colderleo's blog Colderleo's blog
首页
Linux
C++
Python
前端
工具软件
mysql
索引
关于
GitHub (opens new window)

Colder Leo

热爱代码的打工人
首页
Linux
C++
Python
前端
工具软件
mysql
索引
关于
GitHub (opens new window)
  • bug定位的一些情形
  • c++性能调优,可能的情况
  • total-编程知识点集锦
  • hpc_common.hpp
  • memory order 内存模型
  • 类型推导之auto-template-decltype
  • 完美转发forward源码分析
    • 为什么要用std forward
    • std forward源码
    • forward的必要性
    • decltype测试
  • 左值和右值,右值引用、重载 std-move,引用折叠
  • cmake用法
  • alignas、alignof、sizeof实现内存对齐分配
  • 通过宏定义控制debug打印
  • 程序耗时性能测试
  • 线程池开源项目阅读
  • C++类中包含没有默认构造函数的成员
  • C++可变参数模板
  • C++属性继承 public protected private
  • C++智能指针
  • C++导出so的方法,以及extern C 注意事项
  • 四种spin lock
  • condition_variable和unique_lock
  • dpdk、kernel bypass
  • 智能网卡solarflare、Mellanox、X10等
  • 汇编寄存器和常见指令
  • c++ 类的静态成员变量未定义
  • C++获取类成员函数地址
  • preload示例
  • C++异常安全和RAII
  • C++11单例模式
  • C++歪门邪道
  • boost-program-option用法
  • c++17通过模板获取类成员的个数
  • 通过模板实现结构体成员拷贝-按成员名匹配
  • STL学习路径
  • boost库安装使用方法
  • C++文件读写
  • linux下socket通信demo,server和client
  • makefile写法
  • RxCpp
  • C++
gaoliu
2023-04-30
目录

完美转发forward源码分析

# 为什么要用std forward

参考:https://blog.csdn.net/xiangbaohui/article/details/103673177 (opens new window) 这篇文章讲得非常好,建议读懂了再往下看。

关键点总结: 右值引用变量通过decltype看其类型是右值引用,但是变量本身是左值,当其作为参数进行函数重载时,会选择左值的重载。

# std forward源码

下面是std forward的源码

// Forward an lvalue.
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

// Forward an rvalue.
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
        " substituting _Tp is an lvalue reference type");
    return static_cast<_Tp&&>(__t);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

下面把forward改成forward2,并针对上面参考文章中forward的典型用法写一个demo测试,分析forward的具体工作原理。

#include <iostream>
using namespace std;

// 打印一个类型的ref属性。 采用宏定义而不是函数,是为了防止函数传参导致类型变化
#define print_ref_type(tag, T) do{                            \
    printf("%s", tag);                                        \
    if(std::is_lvalue_reference<T>::value) printf(" & \n");   \
    if(std::is_rvalue_reference<T>::value) printf(" && \n");  \
    if(!std::is_reference<T>::value) printf(" NR \n");        \
}while(0)


// Forward an lvalue.
template<typename _Tp>
constexpr _Tp&& forward2(typename remove_reference<_Tp>::type& __t) noexcept
{
    printf("forward2 & func entered.\n");
    print_ref_type("  __t: ", decltype(__t));
    print_ref_type("  _Tp: ", _Tp);
    print_ref_type("  static_cast<_Tp&&>(__t): ", decltype(static_cast<_Tp&&>(__t)));
    return static_cast<_Tp&&>(__t); 
}

// Forward an rvalue.
template<typename _Tp>
constexpr _Tp&& forward2(typename remove_reference<_Tp>::type&& __t) noexcept
{
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
        " substituting _Tp is an lvalue_reference type");
    printf("type&& forward an rrrvalue.\n");
    return static_cast<_Tp&&>(__t);
}


template<typename T>
void fun(T& val) {
	cout << "fun::lvalue: " << val << endl;
}

template<typename T>
void fun(T&& val) {
	cout << "fun::rvalue: " << val << endl;
}

template<typename T>
void test(T&& param) {
    printf("test enter\n");
    print_ref_type("  param: ", decltype(param));
    print_ref_type("  T: ", T);
    print_ref_type("  forward2<T>(param): ", decltype(forward2<T>(param)));

    fun(forward2<T>(param)); //test函数中调用子函数fun时带上forward
}


int main() {
	int a = 1;
	test(a);

  printf("\n");
	test(2);
	return 0;
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

编译运行,输出及解释如下:

$ g++ main.cpp 
$ ./a.out 
test enter      # test函数的`T&& param`要匹配实际输入的int, 但是有通用引用符号&&存在,所以要匹配为int&或者int&&。又因为输入为左值,所以`T&& param`要匹配为int& 
  param:  &     # 故T只能为int&, 这样`T&& param`即为`int& && param`, 折叠为int& 
  T:  & 
  forward2<T>(param):  & 
forward2 & func entered. # forward2有&和&&版本的重载,这里进入&版本的重载
  __t:  & 
  _Tp:  &  #这里_Tp就上面的T, 带引用
  static_cast<_Tp&&>(__t):  &  # 返回值为(_Tp&&)类型,即`int& &&`, 折叠为int&  ======关键在这里_Tp带了个&, 所以返回折叠为左值引用======
fun::lvalue: 1

test enter     # test函数的`T&& param`要匹配实际输入的int&&
  param:  &&   # 所以T为int, 这样`T&& param`即为int&&
  T:  NR  # 没有引用
  forward2<T>(param):  && 
forward2 & func entered. # forward2有&和&&版本的重载,这里进入&版本的重载,因为右值引用的变量本身是左值
  __t:  & 
  _Tp:  NR #这里_Tp就上面的T, 没有引用
  static_cast<_Tp&&>(__t):  && ## 返回值为(_Tp&&)类型,即`int&&`  ======关键在这里_Tp不带&, 所以直接返回int&&右值(注意不是右值引用)======
fun::rvalue: 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# forward的必要性

上面分析了forward的作用原理,但是没讲forward的必要性。如若test()函数中调用子函数fun时去掉forward,即:

    // fun(forward2<T>(param)); //test函数中调用子函数fun时带上forward
    fun(param); //test函数中调用子函数fun时去掉forward
1
2

那么程序输出如下:

$ ./a.out 
test enter
  param:  & 
  T:  & 
  forward2<T>(param):  & 
fun::lvalue: 1  # 直接进入fun的&版本,因为param本身就是&

test enter
  param:  && 
  T:  NR 
  forward2<T>(param):  && 
fun::lvalue: 2  # 仍然进入fun的&版本,因为param变量是右值引用类型,右值引用类型的变量本身还是左值,重载时选择左值版本。
1
2
3
4
5
6
7
8
9
10
11
12

总结:

  • 综上,如果没有forward完美转发,则右值第一次函数入参时为右值引用(本身是左值),函数内调用子函数入参时即为左值。
  • 有了完美转发,子函数调用时加上forward,forward会将右值引用(本身是左值)强制类型转换为右值,子函数入参时仍为右值。

# decltype测试

decltype(右值) 得到的是普通类型,非引用 decltype(右值引用) 得到的是右值引用

  int x=27;

  int & a1 = x;  // a1为int &
  int && a2 = x+2;  //a2为 int&&
  printf("&a2=%p\n", &a2); //可以取地址

  // int && a11 = a1; //编译出错 无法将右值引用绑定到左值, 即a1为左值
  // int && a22 = a2; //编译出错 无法将右值引用绑定到左值, 即a2也为左值, 

  decltype(a2) a3 = x+3; //a3为 int&&, 即decltype(a2)为右值引用
  decltype(a1+7) a4 = x+3; //a4为 int, 即decltype(右值)是表达式本身不带引用的类型

  decltype(x) a5 = x; //a5为 int
  decltype((x)) a6 = x; //a6为 int&
1
2
3
4
5
6
7
8
9
10
11
12
13
14
编辑 (opens new window)
上次更新: 2023/05/07, 17:27:54
类型推导之auto-template-decltype
左值和右值,右值引用、重载 std-move,引用折叠

← 类型推导之auto-template-decltype 左值和右值,右值引用、重载 std-move,引用折叠→

最近更新
01
通过模板实现结构体成员拷贝-按成员名匹配
05-07
02
c++17通过模板获取类成员的个数
05-01
03
avx-sse切换惩罚
04-30
更多文章>
Theme by Vdoing | Copyright © 2019-2023 Colder Leo | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×