c++11:左值引用和右值引用《全家桶》

总结一下C++11中涉及到左值引用和右值引用的场景。

引用:别名,生命时必须初始化、通过引用修改变量值。

1 定义

左值引用

定义:对左值的引用。目的是避免内存拷贝,类似c中的指针,两个场景:函数传参函数返回值。

意外:左值引用也可以引用右值,但是限定为const T&,这样右值就不能修改,如果必须修改,就只能使用右值引用,这也是右值引用发明的初衷之一。

右值引用

定义:对右值的引用。两个场景:实现移动语义实现完美转发。右值引用如果想指向左值,也就是左值转化为右值,通过std::move。

引用虽然分为左值引用和右值引用,这是它的属性,但是这个变量是一个左值。

2 区分

左值引用

可以放在=左边,内存可寻址、具名。eg:变量名、返回左值引用的函数调用、前置自增自减。

右值引用

只能放在=右边,不可寻址、不具名。

两种类型: 纯右值:字面量、返回非引用类型的函数调用、后置自增自减、算数表达式、逻辑表达式、 比较表达式

将亡值:C++11新引入的右值引用(帮助实现移动语义)相关的值类型。触发移动构造和移动赋值构造。

将亡值:用来触发移动构造和移动赋值构造,进行资源转移,之后,调用析构函数销毁。主要的职责是实现资源转移。(资源:IO、FD、堆内存、等)

2.1 移动语义
  1. 移动语义是要解决对象赋值的问题,避免资源的重新分配,实现资源的转移。移动语义的实现,是通过触发移动构造和移动赋值运算符实现的。
  2. STL实现了移动构造接口。比如,将右值(可以是参数直接构造、或者左值的move,结果都是实参为右值)送给push_back方法,会直接移动资源。
  3. unique_ptr
  4. function

在C++11之前,通过已有对象给新对象赋值,使用的是深拷贝。但是在某些场景下,比如IO、FD、堆内存,或者unique_ptr,这种资源是对象独占的,不能二次拷贝,需要从旧的对象转移到新的对象中,这个时候就需要使用右值作为形参,创建移动构造函数或者移动赋值运算法,将旧对象的资源转移的新对象,就对象变为将亡值去析构。

看下拷贝构造和移动构造的使用

int main(int argc, char** argv ){
    cout << "--- copy construction ---" <<endl;
    A a;
    A b(a);
    cout << "--- move construction ---" <<endl;

    A c;
    A d(std::move(c));
    return 0;
}
============== 输出 ==============
(base) ➜  test ./a           
--- copy construction ---
A():p = 0x11e6067e0
A(const A&):p = 0x11e606810
--- move construction ---
A():p = 0x11e606840
A(A&&):p = 0x11e606840
~A() 1
~A() 2
~A() 1
~A() 1

2.2 完美转发

什么是完美转发?

函数模板,可以将自己的参数完美地转发给内部调用的其他函数。

完美指:不仅能准确地转发参数的值,还能保证被转发的参数的左右值属性不变。

如下:在函数模板中的t,不仅能把t的值转发给func函数调用,同时还能把t的左值右值属性传进来。如果t的属性是左值,调用第一个func,如果t的属性是右值,调用第二个func。

void func(int& t){
    cout << "lvalue:"<< t <<endl;
}

void func(int&& t){
    cout << "rvalue:"<< t <<endl;
}

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

怎么解决的完美转发?

1 借用了万能引用

上述代码的T&&就是万能引用,要么使用类型推到auto,要么使用模板参数。具体的类型+&&是右值引用。通过引用的方式接收左右属性的值。

void func(int& t){
    cout << "lvalue:"<< t <<endl;
}
void func(int&& t){
    cout << "rvalue:"<< t <<endl;
}
template<typename T>
void revoke(T && t){ // 接收。不管t的属性是左值引用还是右值引用,t本身始终是一个左值。
    func(std::forward<T>(t));//转发
}
int main(int argc, char** argv )
{
    int i = 10;
    revoke(10);// 解析为右值
    revoke(i);// 解析为 int& 左值引用
}
========= 输出 =========
(base) ➜  test ./a           
rvalue:10
lvalue:10

万能是如何正确解析引用的属性的?

引用折叠只处理接收参数的问题

参数是左值或左值引用,T& && (第一个是值的属性,后两个来自万能引用折叠) 转化为T&

参数是右值或右值引用,T&& &&(第一个是值的属性,后两个来自万能引用折叠)转化为T&&

本质:提取参数的属性,看是左值还是右值。

// 只有在模板推到中,才允许多个引用符号
revoke(static_cast<int & n>);// int& && t   --> int& t
revoke(static_cast<int && n>);// int&& && t --> int&& t

std::forward<T>(v) 

主要作用如下:

  1. 完美转发(Perfect Forwarding)std::forward 主要用于在泛型代码中完美转发参数。当你在一个模板函数中接收参数并希望将它们传递给另一个函数时,你可以使用 std::forward 来保持参数的值分类。这样可以确保参数被转发到下游函数时保持原始的左值或右值属性,从而避免额外的拷贝或移动操作。

  2. 保留值分类(Preserve Value Category)std::forward 可以确保参数在传递过程中保持其原始的值分类,即左值保持为左值,右值保持为右值。这对于支持完美转发的函数模板至关重要。

  3. 针对1和2也可以从解引用的角度理解,将左(右)值引用的引用解除,得到左(右)值。

综上:main函数中,将左值或者右值传递给revoke,revoke形参是万能引用,通过引用折叠确保可以同时接收和保持实参的值属性,但是t本身实际是一个左值,如何提取其指向的值的左值和右值属性呢?通过forward来保留值属性,传递给下游的函数。。

参考测试代码:

#include <iostream>

using namespace std;



class A{
public:
    A(){
        p = new int[10];
        cout << "A():p = " << p <<endl;
    }
    // 拷贝构造函数:深拷贝
    A(const A& a){
        this->p = new int[10];
        memcpy(this->p, a.p, 10 * sizeof(int));
        cout << "A(const A&):p = " << p <<endl;
    }
    ~A(){
        if(p != nullptr){
            delete [] p;
            p = nullptr;
            cout << "~A() 1" <<endl;
        }
        else{
            cout << "~A() 2" <<endl;
        }
    }

    A(A&& a){
        this->p = a.p;
        a.p = nullptr;
        cout << "A(A&&):p = " << p <<endl;
    }
private:
    int* p;
};
int main(int argc, char** argv )
{
    cout << "--- copy construction ---" <<endl;
    A a;
    A b(a);
    cout << "--- move construction ---" <<endl;

    A c;
    A d(std::move(c));
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值