行行宜行行

热爱风光、热爱新技术、踏出每一步,过好每一天!

万能引用与完美转发

万能引用(Universal References)和完美转发(Perfect Forwarding)

一、概述

万能引用和完美转发是C++中两个非常重要的概念,是C++11以及以后版本中构成移动和转发机制的基础,本篇文章从底层原理出发
详细介绍这两个概念。

二、万能引用

1、概念

万能引用,也被称为转发引用(Forwarding Reference),是一种特殊的引用类型(所以核心本质是引用),既可以绑定到左值也可以绑定到右值。
语法形式为 T&&,但是需要满足两个条件:

  1. T必须是可推导的类型(例如模板参数 typename Tauto等)
  2. T必须是精确的类型,不能有其他的修饰(例如constvolatile等)
    具体的形式如下:
template<typename T>
void func(T&& arg);  /// T是万能引用

auto&& var = expr;  /// var是一个万能引用,因为是auto推导

2、底层原理

万能引用的底层原理是基于C++的引用折叠规则,当涉及到模板类型推导和引用类型时,可以按照如下规则对T以及表示参数param进行推导:

  1. T& & 折叠为 T&
  2. T&& & 折叠为 T&
  3. T& && 折叠为 T&
  4. T& && 折叠为 T&&
    结合模板类型的推导规则,当传递左值时,T被推导为T&,当传递右值时,T被推导为T,我们可以从下面的例子中明显得出结果:
template <typename T>
void func(T&& param);

int x = 1;
func(x);        /// 这里传递x,x是一个左值,所以T被推导为int&,param被推导为 int& && -> int&
func(1);        /// 这里传递纯右值1, T被推导为int,param被推导为 int && -> int &&

三、完美转发

1、概念

完美转发是指在函数调用过程中,一个函数将接收到的参数保持其原有的类别(左值/右值)和属性(const等)传递给另一个函数。通过std::forward来实现。
一般在写通用函数,如工厂函数、封装函数等类型的函数时,我们通常不希望参数的属性因为中转一次或多次之后改变,如果不适用完美转发,在一些情况下会将右值变为左值,类中的移动构造与移动赋值也不会起到作用。

2、底层原理‘

完美转发的核心是std::forward,依赖于引用折叠和条件类型的转换,我自己实现的my::forward源码如下:

namespace my {
    /// forward 用于完美转发,保持参数的引用类型
    /// 根据传入的模板参数 T,将参数按照它原本的左值/右值类型转发出去,不发生意外的值类别退化
    /// 下面是左值的情况  T 可以是 T、T&、T&&  --> t 是 remove_reference_t<T>&,也就是说 t 本身是个左值引用
    /// 返回 T&&:即根据模板参数 T 的引用性质返回左值或右值引用
    template<typename T>
    inline constexpr T &&forward(remove_reference_t<T> &t) noexcept {
        return static_cast<T &&>(t);
    }

/// 右值引用的情况
    template<typename T>
    inline constexpr T &&forward(remove_reference_t<T> &&t) noexcept {
        return static_cast<T &&>(t);
    }
}

使用关键字inline标注为内联函数,确保在使用过程中不会被导入多次,constexpr表示在编译期完成,提高性能。

3、使用示例

我们通过下方基础示例代码进一步理解完美转发:

#include <iostream>
#include <utility>

/// 接收左值的函数
void g(int& x) {
    std::cout << "g(int&): " << x << std::endl;
}

/// 接收右值的函数
void g(int&& x) {
    std::cout << "g(int&&): " << x << std::endl;
}

/// 完美转发包装器
template<typename T>
void wrapper(T&& arg) {
    g(std::forward<T>(arg));
}

int main() {
    int x = 42;
    
    wrapper(x);  /// 传递左值,调用g(int&)
    wrapper(123); /// 传递右值,调用g(int&&)
    
    return 0;
}

四、总结

1、 区别

  1. 万能引用的本质是一种特殊的引用类型,需要通过模板类型推导和引用折叠规则实现对左值和右值引用的通用绑定,一般用于实现通用函数
  2. 完美转发是一种参数转发机制,核心是利用std::forward和引用折叠,保持参数的原始属性,确保不会因为中转而变化属性。

2、 联系

万能引用一般与完美转发配合使用,可以实现高效的参数传递和转发。

3、 注意事项

  1. 万能引用和右值引用的区别,只有右值引用满足了模板推导和,精确形式不包含任何修饰的情况才能是万能引用。
  2. 完美转发中应该避免使用std::move(),会将参数强制变为右值引用,破坏了完美转发的性质。

最后,该篇文章也许还存在一些错误和不完整的地方,敬请指出,接收一切批评,希望大家一起努力。如有侵权,请联系我删除。

Next Article

发表回复

Your email address will not be published. Required fields are marked *.

*
*

近期评论

您尚未收到任何评论。

conviction

要想看到璀璨的日出时刻,就要有跋山涉水的决心和坚毅