C++中的万能引用,引用折叠,完美转发

前提

在看别人写的一些库时,总是会碰见万能引用,引用折叠,完美转发这几个概念,这次对它们做一个详细的整理。

万能引用

万能引用 是一个特殊的引用类型,经常在模版编程中使用。它即可以绑定到左值,也可以绑定到右值,也就是说,使用万能引用的函数参数即可以接受左值,也可以接受右值

万能引用是通过以下条件识别的:

  1. 在函数模版中,参数类型是T&&的形式
  2. T是一个模版参数

当这两个条件都满足的时候,T&&被称为万能引用。万能引用的特别之处在于它可以绑定到左值、右值以及常量对象和非常量对象。

一个示例如下:

#include <iostream>
#include <utility> // for std::forward

// 使用万能引用的模板函数
template<typename T>
void forwarder(T&& arg) {  // T&& 被称为万能引用
    std::cout << "num: " << arg << std::endl;
}

int main() {
    int a = 42;

    forwarder(a);     // 即可以传递左值
    forwarder(42);    // 也可以传递右值

    return 0;
}

为什么需要万能引用

万能引用的主要用途是实现完美转发。接下来我们介绍完美转发。

引用折叠

在介绍完美转发之前,我们先介绍一个完美转发中的概念——引用折叠

一个模版函数,根据定义的形参和传入的实参的类型,我们可以有下面四种组合:

  1. 函数定义的形参类型是左值引用T&,传入的实参是左值引用&
  2. 函数定义的形参类型是左值引用T&,传入的实参是左值引用&&
  3. 函数定义的形参类型是右值引用T&&,传入的实参是左值引用&
  4. 函数定义的形参类型是右值引用T&&,传入的实参是右值引用&&

所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。引用折叠的规则很简单:如果任一引用为左值引用,则结果为左值引用。只有两个都是右值引用,结果才为右值引用。

所以上述四种组合经过折叠后,结果如下:

  1. T& &折叠为T&
  2. T& &&折叠为T&
  3. T&& &折叠为T&
  4. T&& &&折叠为T&&

我们来看一个例子:

#include <iostream>

// 泛型函数,展示引用折叠
template<typename T>
void referenceCollapsing(T&& arg) {
    // 打印参数类型
    if constexpr (std::is_lvalue_reference<decltype(arg)>::value) {
        std::cout << "arg is an lvalue reference" << std::endl;
    }
    else if constexpr (std::is_rvalue_reference<decltype(arg)>::value) {
        std::cout << "arg is an rvalue reference" << std::endl;
    }
    else {
        std::cout << "arg is not a reference" << std::endl;
    }
}

int main() {
    int x = 42;

    referenceCollapsing(x);     // x 是左值,T 推导为 int&,折叠后arg的类型为 int&
    referenceCollapsing(42);    // 42 是右值,T 推导为 int, 折叠后arg的类型为 int&&

    return 0;
}

上面的这个例子应该能说明折叠引用的问题。

接下来看这份代码:

#include <iostream>
#include <utility>

void process(int& x) {
    std::cout << "Processing lvalue: " << x << std::endl;
}

void process(int&& x) {
    std::cout << "Processing rvalue: " << x << std::endl;
}

template<typename T>
void forwarder(T&& arg) {
    process(arg);
}

int main() {
    int a = 5;

    forwarder(a);    
    forwarder(10);   

    return 0;
}

简单分析下:forwarder模版函数使用了万能引用。在main函数中,调用了两次forward函数,一次传入的参数是左值a,一次传入的参数是右值10。同时重载了process函数,一个形参类型是int&,一个形参类型是int&&

代码运行的结果如下:

Processing lvalue: 5
Processing lvalue: 10

当传入参数为左值a时,运用引用折叠的规则,此时arg的类型为int&,所以调用process(int& x)
当当传入参数为右值10时,运用引用折叠的规则,此时arg的类型为int&&应该调用process(int&& x)

但是从结果来看,第二次调用了process(int& x)。为什么呢?
因为在forwarder的函数体内,虽然arg的类型为右值引用,但是它也有了变量名称,也可以取地址,所以当forwarder函数内部调用其它函数并将arg作为参数时,此时arg变为了左值。

那么如何解决这个问题呢?这就要用到完美转发了。

完美转发

完美转发(Perfect Forwarding)是C++11引入的一种技术,用于在函数模板中将参数“完美地”传递给另一个函数。这意味着参数的值类别(左值或右值)和所有修饰符(如 const、volatile 等)都能被保留,从而使被调用的函数能够正确处理参数。

std::forward

std::forward 是 C++11 引入的一个标准库函数模板,用于在泛型编程中实现完美转发(perfect forwarding)。它的主要作用是保持传入参数的值类别(左值或右值)不变地传递给另一个函数。

基本用法

测试代码如下:

#include <iostream>
#include <utility> // 包含 std::forward
#include <type_traits>

// 普通函数重载,分别处理左值和右值
void process(int& x) {
    std::cout << "Processing lvalue: " << x << std::endl;
}

void process(int&& x) {
    std::cout << "Processing rvalue: " << x << std::endl;
}

// 泛型函数,用于转发参数
template <typename T>
void forwarder(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int a = 5;
    
    forwarder(a);    // 传递左值
    forwarder(10);   // 传递右值

    return 0;
}

运行结果为:

Processing lvalue: 5
Processing rvalue: 10

这下就对了。

参考链接

  1. C++中的万能引用和完美转发
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值