C++11 :右值引用的作用,引用折叠和完美转发

目录

一.右值引用的作用

1.1为什么要引入右值引用?

二.类型分类(了解)

三.引用折叠

四.完美转发


一.右值引用的作用

回顾左值引用的作用:

  1. 充当别名,避免拷贝,直接绑定左值,操作同一块内存;
  2. 允许修改原始对象;
  3. 函数可以向外带出修改结果(输出型参数);
  4. 作为返回值,使表达式成为左值,支持连续赋值(如 operator[]);

1.1为什么要引入右值引用?

通过重载区分左右值,针对即将销毁的右值实现移动语义,转移资源而不拷贝数据,消除不必要的内存复制开销。

比如:

在这个例子中传值返回需要创建临时对象,然后再拷贝到外面,这会造成两次构造,导致资源浪费,那么就有同学说了,现在编译器都优化了,构造减少了,资源浪费就少了,还有必要用到右值引用吗?我们先了解为什么编译器要优化

传统只有左值引用传值传参中为了避免造成资源,通常用下面这种方法来写:

形参用引用来接收,改变形参的同时就可以改变外面元素了,但是这里缺少可读性,所以编译器要进行优化来解决该问题

再讲之前我们先来了解一下什么是拷贝构造,拷贝赋值,移动构造和移动赋值

函数 函数签名 时机 数据源类型 核心行为
拷贝构造 string(const string& s) 创建新对象(初始化阶段) 左值 /const 左值 深拷贝,新建内存复制字符
移动构造 string(string&& s) 创建新对象(初始化阶段) 右值(临时、std::move) 资源转移,swap,不拷贝数据
拷贝赋值 string& operator=(const string& s) 对象已存在,赋值覆盖 左值 /const 左值 释放自身旧资源,深拷贝新数据
移动赋值 string& operator=(string&& s) 对象已存在,赋值覆盖 右值(临时、std::move) swap 转移资源,无数据拷贝

总结:拷贝构造和拷贝赋值需要消耗资源,移动构造和移动赋值不需要消耗资源

给出下列代码是为了和库里做区分,可帮助我们看出调用了那些构造

cpp 复制代码
namespace Jam
{
    class string
    {
    public:
        typedef char* iterator;
        typedef const char* const_iterator;

        iterator begin()
        {
            return _str;
        }
        iterator end()
        {
            return _str + _size;
        }

        const_iterator begin() const
        {
            return _str;
        }

        const_iterator end() const
        {
            return _str + _size;
        }

        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            //cout << "string(char* str)-构造" << endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        // 拷贝构造
        string(const string& s)
        {
            //cout << "string(const string& s) -- 拷贝构造" << endl;

            reserve(s._capacity);
            for (auto ch : s)
            {
                push_back(ch);
            }
        }

        // 移动构造
        string(string&& s)
        {
            //cout << "string(string&& s) -- 移动构造" << endl;
            swap(s);
        }

        string& operator=(const string& s)
        {
            //cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
            if (this != &s)
            {
                _str[0] = '\0';
                _size = 0;

                reserve(s._capacity);
                for (auto ch : s)
                {
                    push_back(ch);
                }
            }

            return *this;
        }

        // 移动赋值
        string& operator=(string&& s)
        {
            //cout << "string& operator=(string&& s) -- 移动赋值" << endl;
            swap(s);
            return *this;
        }

        ~string()
        {
            //cout << "~string() -- 析构" << endl;
            delete[] _str;
            _str = nullptr;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                if (_str)
                {
                    strcpy(tmp, _str);
                    delete[] _str;
                }
                _str = tmp;
                _capacity = n;
            }
        }

        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }

            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }

        const char* c_str() const
        {
            return _str;
        }

        size_t size() const
        {
            return _size;
        }
    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
    };

    // 传值返回需要拷贝
    string addStrings(string num1, string num2,string& str) {
        //string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        // 进位
        int next = 0;
        while (end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;
            str += ('0' + ret);
        }
        if (next == 1)
            str += '1';
        reverse(str.begin(), str.end());

        return str;
    }
}

注:

图一是在Linux上不带优化运行的结果,图二是一代编译器优化的结果,图三是二代编译器优化后的结果

红色框:外部传入函数时需要创建临时对象调用构造然后进行拷贝构造,我们可以看出编译器优化后直接合二为一没有拷贝构造了

绿色框:函数里面临时变量的构造

蓝色框:传值返回时调用的是构造函数创建临时对象,然后再进行拷贝构造,后面可以看出不断被优化,甚至最后只创建了临时对象,里面的局部对象都没有创建

右值引用有移动构造的场景:

通过观察最后一张图,有人就会说,既然编译器都优化了,最后只剩构造,那还要移动构造干什么?

1.不同编译器对优化也不统一,有些编译器优化,有些编译器不优化,所以还是有必要用到移动构造的

2.下面的移动赋值可以体现出右值引用的价值

注:

这里我们可以看出虽然编译器有优化,但是还是会有拷贝赋值这种情况,我们来看一下有移动赋值的情况

虽然这里最后也有移动赋值,但是相比较于拷贝赋值,移动赋值不消耗资源,所以还是有价值的

深拷贝的自定义类型:如 vector/string/map.... 实现移动构造和移动赋值是有很大的价值的

浅拷贝的自定义类型:如 Date/pair<int, int>....不需要实现移动构造和移动赋值。

二.类型分类(了解)

三.引用折叠

出现场景:

只有模板 / 万能引用(T&&)推导的时候才会发生引用折叠! 普通代码手动写 int& && 直接编译报错,不存在折叠。

当类型推导产生引用叠加时,遵循:

只要存在左值引用 &,结果最终就是左值引用 &; 只有两个都是右值引用 &&,结果才是右值引用 &&

  1. T& & → T&
  2. T& && → T&
  3. T&& & → T&
  4. T&& &&→ T&&

口诀:左值引用传染,遇 & 得 &;全 && 才是 &&

万能模板:

cpp 复制代码
template<class T>
void Function(T&& t)
{
    int a = 0;
    T x = a;
    //x++; // 部分场景下T带const,打开会报错
}
int main()
{
    // 场景1:传入右值 10
    Function(10);
    // 10是纯右值 → T推导为 int
    // T&& = int&& → 实例化:void Function(int&& t)

    // 场景2:传入普通左值变量 a
    int a;
    Function(a);
    // a是左值 → T推导为 int&
    // T&& = int& && →【引用折叠】→ int&
    // 实例化:void Function(int& t)

    // 场景3:std::move(a) 亡值(右值)
    Function(std::move(a));
    // move(a)是右值 → T推导为 int
    // T&& = int&& → void Function(int&& t)

    // 场景4:const左值 const int b
    const int b = 8;
    Function(b);
    // const左值 → T推导为 const int&
    // T&& = const int& && → 折叠 → const int&
    // 实例化 void Function(const int& t)
    // T = const int&,所以 T x = a; 得到 const int& x;x不可修改,x++编译报错

    // 场景5:std::move(b) const右值
    Function(std::move(b));
    // move(const变量) 是const右值
    // T推导为 const int
    // T&& = const int&&
    // T = const int,T x = a → const int x;依然不能x++
    return 0;
}

万能引用推导规律(背诵)

调用 Function(arg)

  1. arg 是非 const 左值 → T = 类型& → 折叠 → 参数 类型&(左值引用)
  2. arg 是 const 左值 → T = const 类型& → 折叠 → 参数 const 类型&
  3. arg 是右值(纯右值 /std::move 亡值) → T = 类型 → 参数 类型&&
  4. arg 是 const 右值 → T = const 类型 → 参数 const 类型&&

传左值,T 推导成引用类型;传右值,T 推导成普通值类型。

四.完美转发

相关推荐
code2cat1 小时前
Java进阶篇之StampedLock:先读取,再校验,必要时退回读锁
java·开发语言
光依旧1 小时前
MCP实战手记(九):生产化MCP Server的6层安全防护
java·人工智能·spring boot·安全·网络安全·ai agent·mcp
阳光九叶草LXGZXJ1 小时前
Linux-学习-12-JDK安装
java·linux·运维·学习
马剑威(威哥爱编程)1 小时前
【AI全栈后端12-04】Spring Boot 用结构化输出自动解析简历:让模型按你的 POJO 输出
java·人工智能·spring boot·后端
阿里嘎多学长2 小时前
2026-09-30 GitHub 热点项目精选
开发语言·程序员·github·代码托管
孙启超3 小时前
【AI开发之Rust】第 22 课:一键多平台与工程收尾 —— CI、发布检查与结课
开发语言·后端·rust
xcl09258 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐10 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长10 小时前
w214基于jsp知道特产网
java·intellij-idea