引用折叠、万能引用、右值引用、move、完美转发

本文详细解释了C++中引用折叠的概念,以及左值引用、右值引用的区别,介绍了万能引用和完美转发在模板编程中的应用,展示了如何通过move和std::forward优化性能。

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

大家好,我叫徐锦桐,个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家访问。

在写STL源码的时候遇到的问题,在这里写一篇笔记记录一下。

一、引用折叠

引用折叠表示了一个值被引用多次时(只有在模板推导时候),会生成什么类型。

T& & 折叠成 T&
T& && 折叠成 T&
T&& & 折叠成 T&
T&& && 折叠成 T&&

其实总结起来就是,只有两次都是右值引用的时候才是T&&,即T&& && 折叠成 T&&,其他都是转换成左值引用T&。

二、左值引用

左值引用,就是个别名,将一个变量绑定到另一个变量上。

int a = 10;
int &b = a;     // 左值引用
const int &c = 10;  // 常量左值引用

b = 20;
std::cout << a << std::endl;  // 20

修改引用变量会影响原来的变量的值,但是 const int & 不能修改原对象的值(中间的int可以更换为其他类型)。
一般来说,函数传参的时候是拷贝传参,也就是将变量复制到一个临时变量,再将临时变量传入函数,最后销毁临时变量。这里涉及到了内存的拷贝,消耗的时间比较长。但是如果在传参的时候使用左值引用,不会涉及到内存的拷贝,因为是别名,相当于直接修改原变量了。

void left_value(int& x) {
    x = 200;
}

int main()
{  
    int value = 10;
    left_value(value);
    std::cout << value << std::endl;  // 200
    return 0;
}



当然如果是const&的话,虽然不涉及到内存的拷贝过程,但是不能修改原变量。

void left_value(const int& x) {
    x = 200;     // error: assignment of read-only reference 'x'
}

int main()
{  
    int value = 10;
    left_value(value);
    std::cout << value << std::endl;  // 200
    return 0;
}

这里函数传参的时候,为什么 int 能匹配到 int& 或者 const int& 呢?

当你将一个int类型传递给int&参数时,c++会执行一个类型转换,将int隐式的转换为int&,另一个同理。注意:int、int&、const int& 在匹配函数的时候优先级一样,同时出现会发生歧义错误。
函数传参的时候隐式转换很常见。

void left_value(int num) {
    std::cout << num << std::endl;
}

int main()
{  
   float a = 1.5;
   left_value(a);    // 1
   return 0;
}

传入的时候隐式的将float类型转化为int类型。


但是用函数重载就可以解决这个问题。

void left_value(int num) {
    std::cout << num << std::endl;
}

void left_value(float num) {
    std::cout << num << std::endl;
}

int main()
{  
   float a = 1.5;
   left_value(a);
   return 0;
}

会调用最适合的函数,那如果没有的话就只能进行隐式的类型转换了。

三、右值引用

右值引用是c++11引用的新特性。就是左值引用是给一个变量加别名,而右值引用就是绑定变量到一个临时值上,临时变量的生命周期和新左值的生命周期绑定,原来的临时变量销毁。

int &&d = 10;   // 右值引

d = 200;  // 此时 d 是变量是左值

引用在c++中是一个特别的类型,因为它的值类型和变量类型不一样, 左值/右值引用变量的值类型都是左值, 而不是左值引用或者右值引用。 这句话意思非常重要,简单来说,看上面代码,一开始将d绑定到了10上面,然后又将d赋值,在int &&d=10 之后,每次使用d,d都是作为一个左值使用的。一个右值引用变量在使用上就变成了左值,已经不再携带其是右引用这样的信息,只是一个左值。


右值引用也是提高性能用的。

vector<string> v;
string s = "teststring";
v.push_back(s);

上面这个代码,用了一个临时变量。假如我们啥都不干那么是怎么进行的呢,首先会先创建一个临时字符串,然后将这个临时字符串拷贝到v的内存上,然后再销毁这个临时字符串。中间进行内存的开辟和销毁,在一般数据上性能没啥问题,如果是数据特别多的话,性能就会严重下降。


但如果我们用右值引用,如下代码。

vector<string> v;
string s = "teststring";
v.push_back(std::move(s));

这里的std::move()是将一个左值强制转换为右值,下面我会讲,现在就知道能将左值强制转化为右值就行了。
中间的过程就变成了,创建一个临时字符串,然后直接将这个临时字符串挂载到v上面。是不是中间少了好多过程,性能也大大的提高了。

四、万能引用

模板编程中,有的时候需要传入左值也传入右值引用。
下面这个只能传入一个左值,如果传入右值就会报错。

template <typename T>
void func(T& value) {
    std::cout << "函数调用" << std::endl;
}

int main()
{
    int value = 10;
    func(value);
    func(10); // 报错
    return 0;
}



这时候就用到了我们的万能引用T&&

template <typename T>
void func(T&& value) {
    std::cout << "函数调用" << std::endl;
}

int main()
{
    int value = 10;
    func(value);
    func(10); 

    return 0;
}

解释一下:
能同时传入左值引用和右值引用。
如果是左值,T先会推导成T&,然后再发生引用折叠,如果是右值引用,T会推导成T&&。
比如说,func(value)中的value是一个左值,传入后T会推导成T&,然后再后后面的&&发生折叠引用,就会变成T&。
func(10)中的10是一个右值,T会推导成T&&,然后再和后边的&&发生引用折叠,最后变成了T&&。
**T&&只在

五、move

std::move它其实啥都没干,就是强制将左值转化为右值。
show you the code

template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

这里面的 std::remove_reference<_Tp>,就像去除变量的引用,假如_Tp是int&&,最后返回type是个int。


std::remove_reference<_Tp>的源代码如下。

/// remove_reference
  template<typename _Tp>
    struct remove_reference
    { typedef _Tp   type; };

  template<typename _Tp>
    struct remove_reference<_Tp&>
    { typedef _Tp   type; };

  template<typename _Tp>
    struct remove_reference<_Tp&&>
    { typedef _Tp   type; };

  template<typename _Tp, bool = __is_referenceable<_Tp>::value>
    struct __add_lvalue_reference_helper
    { typedef _Tp   type; };

  template<typename _Tp>
    struct __add_lvalue_reference_helper<_Tp, true>
    { typedef _Tp&   type; };

六、完美转发

完美转发是配合万能引用使用的,通过万能引用传入左值和右值的参数,然后通过完美转发保留这个属性,转发给对应的重载函数。
show the code

/*
    函数模板的重载
*/
template <typename T>
void to_forward(T& value) {
    std::cout << "调用的左值函数" << std::endl;
}

template <typename T>
void to_forward(T&& value) {
    std::cout << "调用的右值函数" << std::endl;
}

template <typename T>
void func(T&& arg) {

    to_forward(arg);  // 调用左边值函数
}

int main(){
    int value = 10;
    func(std::move(value));
}

在这个代码中,func函数中的to_forward函数最终会调用to_forward(T& value),虽然传入的是右值,一个右值引用变量在使用上就变成了左值,已经不再携带其是右引用这样的信息,只是一个左值,所以这里调用的是左值的重载函数。


首先展示一下std::forward的源码。

/**
   *  @brief  Forward an lvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

  /**
   *  @brief  Forward an rvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  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);
    }

这里有两个重载的函数,一个对应左值一个对应右值。
forward(typename std::remove_reference<_Tp>::type& __t)这个是先将_t的类型去掉引用,然后加上个&,也就是最后是左值类型。
那下面forward(typename std::remove_reference<_Tp>::type&& __t)那个肯定就是右值的了。


这里也用到了引用折叠,连个返回的方法是一样的,都是return static_cast<_Tp&&>(__t),如果_Tp是&,然后在和后面的&&折叠就是左值了;如果_Tp是&&,然后再和后面的&&折叠还是&&。


std::move和std::forward其实什么都没做,只是强制转换了一下类型。


看下面这个代码,这个是正确的。我们通过完美转发保留了这个左值还是右值的属性,然后再传给另一个函数。

#include <iostream>
#include <utility>

/* 完美转发 + 万能引用 + std::move */

/*
    函数模板的重载
*/
template <typename T>
void to_forward(T& value) {
    std::cout << "调用的左值函数" << std::endl;
}

template <typename T>
void to_forward(T&& value) {
    std::cout << "调用的右值函数" << std::endl;
}

/*
    万能引用:
        能同时传入左值引用和右值引用
        如果是左值,T先会推导成T&,然后再发生引用折叠
        入股是右值引用,则会什么都不干
    利用完美转发std::forward:
        先通过万能引用可以传入左值引用和右值引用
        然后通过完美转发(能保留传入时候是左值引用还是右值引用的属性,然后转发到对应的函数重载中
*/
template <typename T>
void func(T&& arg) {
    // 保留左值和右值的属性
    to_forward(std::forward<T>(arg));
}

int main() {

    int value = 10;

    int& value_l_refernce = value;

    func(value);       // 调用的左值引用函数

    func(value_l_refernce);       // 调用的左值引用函数

    func(100);         // 调用的右值引用函数

    int&& value_r_refernce = 30;
    // 右值引用使用后,之后调用这个变量都是作为左值
    func(value_r_refernce);     // **调用的左值引用**

    func(std::move(value));     // 调用的右值引用

    return 0;
}
《RSMA与速率拆分在有限反馈通信系统中的MMSE基预编码实现》 本文将深入探讨RSMA(Rate Splitting Multiple Access)技术在有限反馈通信系统中的应用,特别是通过MMSE(Minimum Mean Square Error)基预编码进行的实现。速率拆分是现代多用户通信系统中一种重要的信号处理策略,它能够提升系统的频谱效率和鲁棒性,特别是在资源受限和信道条件不理想的环境中。RSMA的核心思想是将用户的数据流分割成公共和私有信息两部分,公共信息可以被多个接收器解码,而私有信息仅由特定的接收器解码。这种方式允许系统在用户间共享信道资源,同时保证了每个用户的个性化服务。 在有限反馈通信系统中,由于信道状态信息(CSI)的获取通常是有限且不精确的,因此选择合适的预编码技术至关重要。MMSE预编码是一种优化策略,其目标是在考虑信道噪声和干扰的情况下最小化期望平方误差。在RSMA中,MMSE预编码用于在发射端对数据流进行处理,以减少接收端的干扰,提高解码性能。 以下代码研究RSMA与MMSE预编码的结合以观察到如何在实际系统中应用RSMA的速率拆分策略,并结合有限的反馈信息设计有效的预编码矩阵。关键步骤包括: 1. **信道模型的建立**:模拟多用户MIMO环境,考虑不同用户之间的信道条件差异。 2. **信道反馈机制**:设计有限反馈方案,用户向基站发送关于信道状态的简化的反馈信息。 3. **MMSE预编码矩阵计算**:根据接收到的有限反馈信息,计算出能够最小化期望平方误差的预编码矩阵。 4. **速率拆分**:将每个用户的传输信息划分为公共和私有两部分。 5. **信号发射与接收**:使用预编码矩阵对信号进行处理,然后在接收端进行解码。 6. **性能评估**:分析系统吞吐量、误码率等性能指标,对比不同策略的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐锦桐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值