Cpp 11 / 万能引用、引用折叠和完美转发

本文围绕C++的万能引用、引用折叠和完美转发展开。因C++左、右值引用需函数重载导致代码冗余,诞生万能引用;为判定模板函数中4种引用情况的最终结果,出现引用折叠;为保持引用折叠后数据的引用类型,引入完美转发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、万能引用

1、英文:Universal Reference 

2、诞生的原因

因为 C++ 中存在左值引用和右值引用,导致若想同时实现既可传入左值又可传入右值的功能,需要对相同函数进行重载,导致代码冗余。

3、解决办法

为了解决上述问题,就诞生了万能引用,具体用法如下:

template<typename T>
void func(T&& param) {
    ;
}

主要实现方法是依靠 C++ 强大的模板推导能力,在编译期间确定 param 是左值还是右值。 

4、扩展

&&,在模板形参列表中代表万能引用,在其他地方就代表右值引用。

5、栗子

#include <iostream>

template <typename T>
void func(T &&param)
{
    std::cout << param << std::endl;
}
int main()
{
    int num = 0;
    func(num);
    func(100);
    return 0;
}
0
100

 二、引用折叠

1、英文:Reference Collapse

2、诞生的原因

模板函数:

template <typename T>
void func(T &param)
{
    std::cout << param << std::endl;
}

template <typename T>
void func(T &&param)
{
    std::cout << param << std::endl;
}

 函数形参由于有左值引用和右值引用之分,传入函数的数据也有左值引用和右值引用的区分,这就分出了 4 种情况。

但是 C++ 是不能对引用进行引用的,故需要一种方案,判定上述 4 种情况下最终的结果是左值引用还是右值引用。

3、解决办法

为了解决上述问题,诞生了引用折叠这个概念,也就是判定上述 4 种情况下最终的结果是左值引用还是右值引用的方案,如下:

形参传入数据结果
&&&
&&&&
&&&&
&&&&&&

 可以发现,形参和传入数据只要有一个是左值引用,其结果就是左值引用;只有全部都是右值引用的情况下其结果才能是右值引用。

4、注意

引用折叠只能发生在模板函数中,即:编译期间。

5、栗子

#include <iostream>

template <typename T>
void func_L(T &param)
{
    std::cout << param << std::endl;
}

template <typename T>
void func_R(T &&param)
{
    std::cout << param << std::endl;
}

int getvalue()
{
    return 100;
}
int main()
{
    int num = 0;
    int &&num_r = getvalue();
    func_L(num);
    func_L(num_r);
    func_R(num);
    func_R(200);
    return 0;
}
0
100
0
200

 三、完美转发

1、英文:Perfect Forwarding

2、诞生的原因

经过上述引用折叠之后,传之前的数据的引用类型和传入之后的引用类型可能发生变化,怎么才能保持引用类型呢?如下:

#include <iostream>

template <typename T>
void func(T &param)
{
    std::cout << "左值" << std::endl;
}
template <typename T>
void func(T &&param)
{
    std::cout << "右值" << std::endl;
}

template <typename T>
void warp(T &&param)
{
    func(param);
}

int main()
{
    int num = 0;
    warp(num);
    warp(100);
    return 0;
}
左值
左值

 3、解决办法

为了解决上述问题,增加了完美转发的概念,即:经过转发之后,数据的引用类型恢复到之前的类型。

4、栗子

#include <iostream>

template <typename T>
void func(T &param)
{
    std::cout << "左值" << std::endl;
}
template <typename T>
void func(T &&param)
{
    std::cout << "右值" << std::endl;
}

template <typename T>
void warp(T &&param)
{
    func(std::forward<T>(param));
}

int main()
{
    int num = 0;
    warp(num);
    warp(100);
    return 0;
}
左值
右值

 

(SAW:Game Over!) 

 

<think>我们正在讨论C++中的引用类型:左值引用、右值引用万能引用(转发引用)。根据提供的引用内容,我们需要详细解释它们的作用与区别。 左值引用(Lvalue Reference): - 使用 `&` 声明,例如 `int& ref = x;` - 只能绑定到左值(有名字的对象,可以取地址) - 主要作用:避免对象拷贝,允许通过别名修改原始对象 右值引用(Rvalue Reference): - 使用 `&&` 声明,例如 `int&& ref = 10;` - 只能绑定到右值(临时对象,即将销毁的对象) - 主要作用:实现移动语义(转移资源所有权)完美转发 万能引用(Universal Reference,也称为转发引用): - 在模板推导的上下文中,使用 `T&&` 的形式 - 可以绑定到左值、右值、const、非const等任意类型的值 - 主要作用:配合 `std::forward` 实现完美转发,保持原始值类别 引用折叠规则(Reference Collapsing): - 当模板参数推导遇到引用引用时,会折叠成单一引用: T& & -> T& T& && -> T& T&& & -> T& T&& && -> T&& 完美转发(Perfect Forwarding): - 使用 `std::forward<T>(t)` 在函数模板内部转发参数时,保持原始参数的值类别(左值或右值) 我们将按照以下结构组织回答: 1. 左值引用详解(定义、特点、使用场景) 2. 右值引用详解(定义、特点、使用场景,移动语义) 3. 万能引用(转发引用)详解(定义、引用折叠完美转发) 4. 三者的区别总结 5. 代码示例 注意:在回答中,我们将使用提供的引用内容作为参考,并在适当位置标注引用标记(如[^1])。 由于用户要求生成相关问题,我们将在最后提出几个相关问题。</think>### C++左值引用、右值引用万能引用详解 #### 1. **左值引用(Lvalue Reference)** **定义**:使用`&`声明的引用,只能绑定到左值(有持久身份的对象) **核心特性**: - 绑定到具名变量或可寻址对象 - 允许修改原始对象(除非声明为`const`) - C++98标准引入,主要用于避免拷贝开销[^1][^3] ```cpp int a = 10; int& ref_a = a; // 左值引用 ref_a = 20; // 修改原始对象a的值 ``` **应用场景**: - 函数参数传递(避免拷贝大对象) - 函数返回引用(支持链式调用) - 实现操作符重载(如`<<`, `>>`) **限制**: ```cpp int& ref = 10; // 错误:不能绑定到右值 const int& cref = 10; // 正确:常量左值引用可绑定右值 ``` --- #### 2. **右值引用(Rvalue Reference)** **定义**:使用`&&`声明的引用,只能绑定到右值(临时对象) **核心特性**: - 绑定到即将销毁的临时对象 - C++11引入,支持移动语义完美转发 - 允许修改右值(转移资源所有权)[^2][^4] ```cpp int&& rref = 10; // 绑定字面量 std::string&& sref = "Hi"; // 绑定临时字符串 ``` **移动语义示例**: ```cpp class Vector { public: // 移动构造函数 Vector(Vector&& src) noexcept : data(src.data), size(src.size) { src.data = nullptr; // 转移资源所有权 } }; ``` --- #### 3. **万能引用(Universal Reference)** **定义**:模板推导上下文中的`T&&`,可绑定任意值类型 **核心特性**: - 根据引用折叠规则自动推导值类别 - 配合`std::forward`实现完美转发 - 保持参数的原始值类别(左值/右值)[^4] **引用折叠规则**: | 原始类型 | 折叠结果 | |---------|---------| | `T& &` | `T&` | | `T& &&` | `T&` | | `T&& &` | `T&` | | `T&& &&` | `T&&` | **完美转发示例**: ```cpp template<typename T> void relay(T&& t) { // 保持t的原始值类别 process(std::forward<T>(t)); } relay(10); // 转发右值 std::string s = "hi"; relay(s); // 转发左值 relay(std::move(s)); // 转发右值 ``` --- ### 三者的核心区别 | 特性 | 左值引用 (`&`) | 右值引用 (`&&`) | 万能引用 (`T&&`) | |--------------------|---------------------|---------------------|---------------------| | **绑定对象** | 左值 | 右值 | 左值/右值 | | **主要目的** | 避免拷贝 | 实现移动语义 | 完美转发 | | **是否可修改** | 是(除非const) | 是 | 取决于原始类型 | | **作用域** | 局部/全局 | 局部/全局 | 模板参数作用域 | | **引入版本** | C++98 | C++11 | C++11 | --- ### 关键应用场景 1. **移动语义优化** ```cpp std::vector<std::string> createStrings() { std::vector<std::string> v; v.push_back("move"); return v; // 触发移动构造而非拷贝 } ``` 2. **完美转发** ```cpp template<class T> void wrapper(T&& arg) { // 精确传递arg的值类别 worker(std::forward<T>(arg)); } ``` 3. **避免临时对象拷贝** ```cpp void process(const std::string& s); // 接受左值 void process(std::string&& s); // 接受右值 process("temp"); // 调用右值版本,避免创建临时string ``` --- ### 总结对比 - **左值引用**:基础引用类型,处理持久对象 - **右值引用**:C++11革命性特性,实现资源转移 - **万能引用**:模板编程的利器,实现值类别无损传递 > **核心准则**: > - 优先按值传递小对象 > - 大对象用`const T&`传递(只读) > - 需要修改用`T&`(左值引用) > - 需要转移资源用`T&&`(右值引用) > - 泛型代码用`T&&`+`std::forward`(万能引用)[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值