• 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,这种资源是对象独占的,不能二次拷贝,需要从旧的对象转移到新的对象中,这个时候就需要使用右值作为形参,创建移动构造函数或者移动赋值运算法,将旧对象的资源转移的新对象,就对象变为将亡值去析构。

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

    1. int main(int argc, char** argv ){
    2. cout << "--- copy construction ---" <
    3. A a;
    4. A b(a);
    5. cout << "--- move construction ---" <
    6. A c;
    7. A d(std::move(c));
    8. return 0;
    9. }
    10. ============== 输出 ==============
    11. (base) ➜ test ./a
    12. --- copy construction ---
    13. A():p = 0x11e6067e0
    14. A(const A&):p = 0x11e606810
    15. --- move construction ---
    16. A():p = 0x11e606840
    17. A(A&&):p = 0x11e606840
    18. ~A() 1
    19. ~A() 2
    20. ~A() 1
    21. ~A() 1

    2.2 完美转发

    什么是完美转发?

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

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

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

    1. void func(int& t){
    2. cout << "lvalue:"<< t <
    3. }
    4. void func(int&& t){
    5. cout << "rvalue:"<< t <
    6. }
    7. template<typename T>
    8. void revoke(T && t){
    9. func(std::forward(t));
    10. }

    怎么解决的完美转发?

    1 借用了万能引用

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

    1. void func(int& t){
    2. cout << "lvalue:"<< t <
    3. }
    4. void func(int&& t){
    5. cout << "rvalue:"<< t <
    6. }
    7. template<typename T>
    8. void revoke(T && t){ // 接收。不管t的属性是左值引用还是右值引用,t本身始终是一个左值。
    9. func(std::forward(t));//转发
    10. }
    11. int main(int argc, char** argv )
    12. {
    13. int i = 10;
    14. revoke(10);// 解析为右值
    15. revoke(i);// 解析为 int& 左值引用
    16. }
    17. ========= 输出 =========
    18. (base) ➜ test ./a
    19. rvalue:10
    20. lvalue:10

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

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

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

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

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

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

    std::forward(v) 

    主要作用如下:

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

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

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

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

    参考测试代码:

    1. #include
    2. using namespace std;
    3. class A{
    4. public:
    5. A(){
    6. p = new int[10];
    7. cout << "A():p = " << p <
    8. }
    9. // 拷贝构造函数:深拷贝
    10. A(const A& a){
    11. this->p = new int[10];
    12. memcpy(this->p, a.p, 10 * sizeof(int));
    13. cout << "A(const A&):p = " << p <
    14. }
    15. ~A(){
    16. if(p != nullptr){
    17. delete [] p;
    18. p = nullptr;
    19. cout << "~A() 1" <
    20. }
    21. else{
    22. cout << "~A() 2" <
    23. }
    24. }
    25. A(A&& a){
    26. this->p = a.p;
    27. a.p = nullptr;
    28. cout << "A(A&&):p = " << p <
    29. }
    30. private:
    31. int* p;
    32. };
    33. int main(int argc, char** argv )
    34. {
    35. cout << "--- copy construction ---" <
    36. A a;
    37. A b(a);
    38. cout << "--- move construction ---" <
    39. A c;
    40. A d(std::move(c));
    41. return 0;
    42. }

  • 相关阅读:
    复习单片机:定时器/计数器部分(内含:1.51 单片机定时器原理 +2.51 单片机定时/计数器结构+3.定时器配置+4.代码部分+5.实验现象)
    python爬虫之requests库post请求
    20220729NOI模拟赛--考后总结
    【Electron】electron与cljs的处理
    python之字符串
    clion qt导出dll给别的项目用
    Python基础学习笔记1(AI Studio)
    PXE高效批量网络装机
    CSS Position定位(详解网页中的定位属性)
    基于Springboot的新闻资讯发布系统设计与实现(源码+lw+部署文档+讲解等)
  • 原文地址:https://blog.csdn.net/flying_ant2018/article/details/139215816