跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++11 右值引用与移动语义详解:从性能瓶颈到零拷贝优化

C++11 引入右值引用与移动语义,旨在解决对象传递中的深拷贝性能开销。文章涵盖 C++11 历史背景、列表初始化语法、左值与右值的本质区别、右值引用绑定规则及生命周期延长机制。重点讲解移动构造函数与移动赋值运算符的实现原理,通过模拟 string 和 list 容器演示资源窃取而非复制的优化效果。结合编译器优化(如 RVO)分析传值返回场景,展示如何利用 std::move 强制转换左值为右值以触发移动操作,从而提升 STL 容器 push_back 等接口的效率。

锁机制发布于 2026/2/5更新于 2026/10/151.3K 浏览
C++11 右值引用与移动语义详解:从性能瓶颈到零拷贝优化

C++11 右值引用与移动语义详解

C++ 学习阶段的三个参考文档

看库文件(非官方文档):cplusplus

这个文档在 C++98、C++11 时候还行,之后就完全没法用了……

还可以看语法——准官方文档(同步更新):C++准官方参考文档

这个行,包括 C++26 都同步了,我们以后会看这个。

有大佬——官方文档(类似论坛):Standard C++

这个网站上面会有很多大佬。

总结一下就是——

前情提示

1 C++ 学习的三个参考文档

2 {}初始化

3 C++11 中的{}

4 引用

5 fmin

6 左值引用和右值引用

1 ~> C++11 的历史发展

1.1 历史发展

既然谈到 C++11 的历史发展,那不得不再次献出这种经典老图——

C++11 是 C++ 的第二个主要版本,并且是自从 C++98 版本开始的最重要的更新。C++11 引入了大量更改,标准化了既有的一些实践,并改进了对 C++ 程序员可用的抽象。在它最终由 ISO 在 2011 年 8 月 12 日采纳前,人们曾使用名称'C++0x'——因为它曾被期待在 2010 年之前发布(结果一直拖到 2011 年)。C++03(没出什么新特征)与 C++11 期间花了 8 年时间!这是迄今为止最长的版本间隔——C++08 烂尾了(五年计划),因为 C++08 的规划太大、特性太多——C++ 标准委员会想做的东西太多,结果来不及了,这导致委员会此后调整了发布策略:

从那时起,C++ 有规律地每 3 年更新一次——能出多少是多少。

1.2 拓展讨论

语言被公司使用是有一个过程的。

**C++ 标准委员会:制定理论语法。

编译器:支持 C++ 语法——可进行有原则性的支持(常用的肯定会支持,否则编译器不好用就会被淘汰的)**

举个例子,C 语言的 C99 标准更新了变长数组(用变量),VS 就不支持(编译器不支持的体现)。

包括像 C++23,大多数编译器还不完全支持——

上面这张图,大家可以了解一下:编译器对几个版本的 C++ 的支持情况。

语言的新的语法标准被大规模使用的缓冲期:5~10 年。

(1)大多数公司就是使用到 C++11,当然也有 40%~50% 的公司在使用 C++14(小版本)、C++17(中版本,不少公司用)。

(2)C++23(公司使用偏少)是个大版本:特性还不够成熟(上层的库还不够完善),学的不错的程序员不多;C++23 就更少了。

VS 编译器:MSVC(微软);苹果编译器:Clang(支持苹果的 ObjectC,也支持 C / C++)。

看编译器是否支持语法特性。

2 ~> 列表初始化:{}

2.1 C++98 中的{}

C++98 中一般数组和结构体可以用 0 进行初始化。

2.2 C++11 中的{}

C++11 以后,想统一初始化方式:试图实现一切对象皆可用{}初始化,{}初始化也叫列表初始化;

内置类型和自定义类型都支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造;

{}初始化的过程中,可以省略掉=;

C++11 列表初始化的本意是想实现一个大统一的初始化方式,其次在有些场景下列表初始化会带来不少便利,如容器 push / inset 多参数构造的对象时,{}初始化会很方便。

2.3 C++11 中的 std::initializer_list(初始化列表)

上面的{}初始化已经很方便了,但是对于对象容器初始化还是不太方便,比如一个 vector 对象,如果我们想用 N 个值去构造初始化,那么我们得实现很多个构造函数才能支持——

vector<int>v1 = {1 , 2 , 3}; 
vector<int>v2 = {1 , 2 , 3 , 4 , 5};

C++11 库中提出了一个 std::initializer_list 的类——

auto il={10,20,30}; //the typeof il is an initializer_list

std::initializer_list 这个类的本质是:底层开一个数组,将数据拷贝过来,std::initializer_list 内部有两个指针分别指向数组的开始和结束。

文档:initializer_list,std::initializer_list 支持迭代器遍历。

容器支持一个 std::initializer_list 的构造函数,也就支持任意多个值构成的{x1 , x2 , x3...}进行初始化。STL 中的容器支持任意多个值构成的{x1 , x2 , x3 , ……}进行初始化,就是 std::initializer_list 的构造函数支持的。

3 ~> 右值引用 && 移动语义

C++98 的 C++ 语法中就有引用的语法,我们前面介绍的就是啦,而 C++11 中新增了的右值引用语法特性,C++11 之后,我们之前学习的引用就叫做左值引用。无论是左值引用还是右值引用,都是给对象取别名(给对象取别名,不开空间)。

3.1 左值和右值

**左值,可以取地址——**左值是一个表示数据的表达式(如变量名或解引用的指针),一般有持久存在,存储在内存中,可以获取它的地址(区别),左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时 const 修饰符后的左值,不能给他赋值。

**右值,不能取地址(区别)——**右值也是一个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象右值可以出现在赋值符号的右边,但是一般不能出现出现在赋值符号的左边。

这里是会编译报错的,根据提示,注释掉后面的 cout 部分就能通过了——

3.2 左值引用和右值引用

3.2.1 概念
Type& r1 = x; 
Type&& rr1 = y;

第一个语句就是左值引用,左值引用就是给左值取别名;第二个语句就是右值引用,同样的道理,右值引用就是给右值取别名——这个左值和右值就是上面说的能不能取到地址的区别。

左值引用不能直接引用右值,但是 const 左值引用可以引用右值; 右值引用不能直接引用左值,但是右值引用可以引用 move(左值)。

template typename remove_reference::type&& move (T&& arg);

move(强转)是库里面的一个函数模板,本质内部是进行强制类型转换,当然这里还涉及一些引用折叠的知识,这个我们后面会详细介绍的,现在先了解一下。

值得注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值。

语法层面看,左值引用和右值引用都是取别名,不开空间。

从汇编底层的角度看下面代码中 r1 和 rr1 汇编层实现,底层都是用指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要放到一起去理解,很容易搞混!

3.2.2 左值引用、右值引用可以相互交叉

左值引用、右值引用可以'相互交叉',如下图所示——

3.2.3 引用延迟生命周期

右值引用可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。如下图,演示了右值引用延长匿名对象的生命周期——

3.2.4 左值和右值的参数匹配问题

C++98 中,我们实现一个 const 左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。

C++11 以后,分别重载左值引用、const 左值引用、右值引用作为形参的 f 函数,那么实参是左值会匹配 f(左值引用),实参是 const 左值会匹配 f(const 左值引用),实参是右值会匹配 f(右值引用)。

右值引用变量在用于表达式时属性是左值(天才的设计!),这个设计这里会感觉有点怪,等我们介绍右值引用的使用场景时,就能体会这样设计的价值了,这里先买个关子。

3.3 右值引用和移动语义的使用场景

3.3.1 左值引用主要使用场景

**左值引用主要使用场景:**函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值。**左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回:**如 addStrings 和 generate 函数,C++98 中的解决方案只能是被迫使用输出型参数解决。C++11 以后这里可以使用右值引用作为返回值来解决吗?显然这是不可能的,因为这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用返回也无法概念对象已经析构销毁的事实。

3.3.2 传值返回需要拷贝
3.3.3 移动构造和移动赋值

移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。

移动赋值是一个赋值运算符的重载,跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。

对于像 string / vector 这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类型,本质是要'窃取'引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从提高效率。下面的 jqj:string 样例实现了移动构造和移动赋值,我们需要结合场景理解。

3.3.4 左值拷贝和右值拷贝:拷贝构造和移动构造

3.4 右值引用和移动语义解决传值返回问题

3.4.1 两种场景实践
3.4.2 传值返回已经没有拷贝了,是否意味上面的右值引用和移动语义就没意义?
3.4.3 右值对象构造,只有拷贝构造,没有移动构造的场景

如下图展示了 vs2019 的 debug 环境下编译器对拷贝的优化,左边为不优化的情况下,两次拷贝构造;右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次拷贝构造。

需要注意的是在 vs2019 的 release 版本和 vs2022 的 debug 和 release 版本,下面代码优化为非常恐怖——会直接将 str 对象的构造,str 拷贝构造临时对象,临时对象拷贝构造 ret 对象,合三为一,变为直接构造。要理解这个优化要结合局部对象生命周期和栈帧的角度理解。

linux 下可以将下面代码拷贝到 test.cpp 文件,编译时用的方式关闭构造优化——

g++ test.cpp -fno-elide-constructors

运行结果可以看到下图中左边没有优化的两次拷贝。

3.4.4 右值对象构造,既有拷贝构造,也有移动构造的场景

如下图展示了 vs2019 的 debug 环境下编译器对拷贝的优化,左边为不优化的情况下,两次移动构造;右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次移动构造。

需要注意的是在 vs2019 的 release 版本和 vs2022 的 debug 和 release 版本,下面代码优化为非常恐怖——会直接将 str 对象的构造,str 拷贝构造临时对象,临时对象拷贝构造 ret 对象,合三为一,变为直接构造。要理解这个优化要结合局部对象生命周期和栈帧的角度理解。

linux 下可以将下面代码拷贝到 test.cpp 文件,编译时用的方式关闭移动优化——

g++ test.cpp -fno-elide-constructors

运行结果可以看到下图中左边没有优化的两次移动——

3.4.5 以上两种情况结合局部对象生命周期和栈帧的角度的理解
3.4.6 右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景

下图的左边展示了 vs2019 的 debug 版本以及——

g++ test.cpp -fno-elide-constructors

关闭优化环境下编译器的处理,一次拷贝构造,一次拷贝赋值。

需要注意的是在 vs2019 的 release 版本和 vs2022 的 debug 和 release 版本,下面代码会进一步优化,直接构造要返回的临时对象,str 本质是临时对象的引用,底层角度用指针实现。运行结果的角度,可以看到 str 的析构是在赋值以后,说明 str 就是临时对象的别名。

3.4.7 右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景

下图的左边展示了 vs2019 的 debug 版本以及——

g++ test.cpp -fno-elide-constructors

关闭优化环境下编译器的处理,一次移动构造,一次移动赋值。

需要注意的是在 vs2019 的 release 版本和 vs2022 的 debug 和 release 版本,下面代码会进一步优化,直接构造要返回的临时对象,str 本质是临时对象的引用,底层角度用指针实现。运行结果的角度,可以看到 str 的析构是在赋值以后,说明 str 就是临时对象的别名。

3.5 右值引用和移动语义在传参中的提效

查看 STL 文档我们发现 C++11 以后容器的 push 和 insert 系列的接口否增加的右值引用版本;

当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象;

当实参是一个右值,容器内部则调用移动构造,右值对象的资源到容器空间的对象上;

把我们之前模拟实现的 bit:list 拷贝过来,支持右值引用参数版本的 push_back 和 insert;

其实这里还有一个 emplace 系列的接口(终于来啦),但是这个涉及可变参数模板,我们需要把可变参数模板介绍完以后再介绍 emplace 系列的接口——涉及引用折叠等内容——

完整代码示例与实践演示

list.h:

#pragma once 
namespace jqj { 
// --------------链表节点结构-------------- 
template<class T> 
struct list_node { 
    list_node<T>* _next; // 指向下一个节点的指针 
    list_node<T>* _prev; // 指向前一个节点的指针 
    T _data; // 节点存储的数据 
    // --------------节点构造函数-------------- 
    // 左值 
    list_node(const T& x = T()) :_next(nullptr) , _prev(nullptr) , _data(x) // x 节点数据,默认为 T 类型的默认值 {} 
    // 右值 
    list_node(T&& x) :_next(nullptr) , _prev(nullptr) , _data(move(x)) // x 节点数据,默认为 T 类型的默认值 {} 
}; 
// --------------链表迭代器-------------- 
// 实现双向迭代器功能,支持前向和后向遍历 
template<class T, class Ref,class Ptr> // T 数据类型 
// Ref 引用类型(T& 或 const T&) 
struct list_iterator { 
    // using 还具有 tepdef 没有的功能 
    // 使用类型别名(C++11 新特性) 
    using Self = list_iterator<T, Ref, Ptr>; // 自身类型 
    using Node = list_node<T>; // 节点类型 
    Node* _node; // 当前指向的节点指针 
    // 迭代器构造函数 
    list_iterator(Node* node) :_node(node) {} 
    // 迭代器解引用操作 
    // *it = 1 
    Ref operator*() // 解引用,Ref 就是 reference,引用的意思 
    { return _node->_data; } 
    Ptr operator->() // 返回对应数据类型的指针 
    { return &_node->_data; } 
    // ++it // 前向迭代操作 
    Self& operator++() // Self& 返回递增后的迭代器引用 
    { _node = _node->_next; return *this; } 
    Self operator++(int) // Self 返回递增前的迭代器副本 
    { Self tmp(*this); _node = _node->_next; return tmp; } 
    // --it // 后向迭代操作 
    Self& operator--() // Self& 返回递减后的迭代器引用 
    { _node = _node->_prev; return *this; } 
    Self operator--(int) // Self 返回递减前的迭代器副本 
    { Self tmp(*this); _node = _node->_prev; return tmp; } 
    // 迭代器比较操作 
    bool operator!=(const Self& s) const // bool 两个迭代器是否不指向同一节点 
    { return _node != s._node; } 
    bool operator==(const Self& s) const // bool 两个迭代器是否不指向同一节点 
    { return _node == s._node; } 
}; 
// --------------链表主体类-------------- 
template<class T> 
class list { 
    using Node = list_node<T>; // 节点类型别名 
public: 
    // 迭代器类型定义 
    using iterator = list_iterator<T, T&, T*>; // 普通迭代器 
    using const_iterator = list_iterator<T, const T&, const T*>; // 常量迭代器 
    // const T* 只能读数据,不能修改数据 
    // --------------迭代器访问接口-------------- 
    // 获取指向第一个元素的迭代器 
    // iterator 指向首元素的迭代器 
    iterator begin() { return iterator(_head->_next); } 
    // iterator 指向哨兵节点的迭代器 
    iterator end() { return iterator(_head); } 
    // 获取指向第一个元素的常量迭代器 
    // const_iterator 指向首元素的常量迭代器 
    const_iterator begin() const { return const_iterator(_head->_next); } 
    // const_iterator 指向哨兵节点的常量迭代器 
    const_iterator end() const { return const_iterator(_head); } 
    // ----------------链表初始化相关----------------- 
    void empty_init() // 初始化空链表(创建哨兵节点) 
    { _head = new Node; _head->_next = _head; _head->_prev = _head; } 
    // 默认构造函数 
    list() { empty_init(); } 
    // 初始化列表构造函数 
    // il 初始化列表 
    list(initializer_list<T> il) { empty_init(); for (auto& e : il) { push_back(e); } } 
    // 范围构造函数 
    // InputIterator 输入迭代器类型 
    template <class InputIterator> 
    list(InputIterator first, InputIterator last) // first 范围起始迭代器 
    // last 范围结束迭代器 
    { empty_init(); while (first != last) { push_back(*first); ++first; } } 
    // 数量构造函数(size_t 版本) 
    list(size_t n, T val = T()) // val 元素值,默认为 T 的默认值 
    { empty_init(); for (size_t i = 0; i < n; ++i) { push_back(val); } } 
    // 数量构造函数(int 版本) 
    list(int n, T val = T()) // val 元素值,默认为 T 的默认值 
    { empty_init(); for (size_t i = 0; i < n; ++i) { push_back(val); } } 
    // -------------析构函数------------- 
    // 清理所有节点并释放哨兵节点 
    ~list() { clear(); delete _head; _head = nullptr; _size = 0; } 
    // -----------拷贝控制函数---------- 
    // lt 要拷贝的源链表 
    // lt2(lt1) 
    list(const list<T>& lt) { empty_init(); for (auto& e : lt) { push_back(e); } } 
    // 拷贝赋值运算符 
    // lt 要拷贝的源链表 
    // list<T>& 返回当前链表的引用 
    // lt1 = lt3 
    list<T>& operator=(const list<T>& lt) { 
        if (this != &lt) { 
            clear(); 
            for (auto& e : lt) { push_back(e); } 
        } 
        return *this; 
    } 
    // ----------------容量操作--------------- 
    // 交换两个链表的内容 
    void swap(list<T>& lt) // lt 要交换的另一个链表 
    { std::swap(_head, lt._head); std::swap(_size, lt._size); } 
    // 清空链表中的所有元素 
    // 保留哨兵节点,删除所有数据节点 
    void clear() { 
        iterator it = begin(); 
        while (it != end()) { 
            it = erase(it); 
        } 
    } 
    // -----------------元素访问操作-------------------- 
    // 复用 
    void push_back(const T& x) // 在链表尾部插入元素,x:要插入的元素值 
    { insert(end(), x); } 
    // 右值:尾插 
    void push_back(T&& x) { insert(end(), x); } 
    // 在链表头部插入元素,x:要插入的元素值 
    void push_front(const T& x) { insert(begin(), x); } 
    // 删除链表尾部元素 
    void pop_back() { erase(--end()); } 
    // 删除链表头部元素 
    void pop_front() { erase(begin()); } 
    // ---------------- 指定位置操作 ---------------- 
    // 在指定位置前插入元素 
    void insert(iterator pos, const T& x) // pos:插入位置的迭代器,x:要插入的元素值 
    { 
        Node* cur = pos._node; 
        Node* prev = cur->_prev; 
        Node* newnode = new Node(x); 
        // // 连接新节点:prev -> newnode -> cur 
        // prev newnode cur 
        prev->_next = newnode; 
        newnode->_prev = prev; 
        newnode->_next = cur; 
        cur->_prev = newnode; 
        ++_size; 
    } 
    // 删除指定位置的元素 
    iterator erase(iterator pos) // iterator 返回指向被删除元素后一个元素的迭代器 
    { 
        Node* cur = pos._node; 
        Node* prev = cur->_prev; 
        Node* next = cur->_next; 
        // 跳过被删除节点:prev -> next 
        prev->_next = next; 
        next->_prev = prev; 
        delete cur; 
        --_size; 
        /*return iterator(next);*/ 
        return next; /// 返回下一个节点的迭代器 
        //两种写法都可以 
    } 
    // -------------- 容量信息 ------------------ 
    size_t size() const { 
        //size_t n = 0; 
        //for (auch e : *this) 
        //{ 
        // ++n; 
        //} 
        //return n; 
        return _size; 
    } 
private: 
    Node* _head; // 哨兵头节点指针 
    size_t _size = 0; // 链表元素个数计数器 
}; 
}

Test.cpp:

#define _CRT_SECURE_NO_WARNINGS 1 
#include<iostream> 
#include<assert.h> 
#include<algorithm> 
#include<string.h> 
using namespace std; 

namespace Alice { 
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*) :_size(strlen(str)) ,_capacity(_size) { 
        cout << "string(char* str)-构造" << endl; 
        _str = new char[_capacity + 1]; 
        strcpy(_str, str); 
    } 
    void swap(string& s) { 
        std::swap(_str, s._str); 
        std::swap(_size, s._size); 
        std::swap(_capacity, s._capacity); 
    } 
    // 拷贝构造 
    string(const string& s) { 
        cout << "string(char* str)-构造" << endl; 
        reserve(s._capacity); 
        for (auto ch : s) { push_back(ch); } 
    } 
    // 移动构造 
    string(string&& s) { 
        cout << "string(char* str)-构造" << endl; 
        swap(s); // 交换后 s 持有原对象的资源 
    } 
    // s 析构时会释放原对象的资源,但原对象现在持有什么? 
    string& operator=(const string& s) { 
        cout << "string(char* str)-构造" << 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(char* str)-构造" << 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 new_capacity) { 
        //int n = 0; 
        // 触发 assert 的报错机制 
        if (new_capacity > _capacity) { 
            char* tmp = new char[new_capacity + 1]; 
            if (_str) { 
                strcpy(tmp, _str); // 包括 null 终止符 
                delete[] _str; 
            } 
            _str = tmp; 
            _capacity = new_capacity; 
        } 
    } 
    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; 
    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()); 
    cout << &str << endl; 
    return str; 
} 
}

// 右值引用和移动语义在传参中的提效 
#include"list.h" 
int main() { 
    jqj::list<Alice::string> lt; 
    cout << "**************************" << endl; 
    Alice::string s1("111111111111111111"); 
    lt.push_back(s1); 
    cout << "**************************" << endl; 
    lt.push_back("2222222222222222222222222222222222"); 
    cout << "**************************" << endl; 
    lt.push_back("3333333333333333333333333333333"); 
    cout << "**************************" << endl; 
    // 左值 move 本质是授予别人转移你的数据资源的权限,所以要谨慎,move 过的左值以后就不能再用了 
    lt.push_back(move(s1)); 
    cout << "**************************" << endl; 
    // 运行结果如下: 
    //string(char* str) - 构造 
    //string(char* str) - 构造 
    //* ************************* 
    //string(char* str) - 构造 
    //string(char* str) - 构造 
    //* ************************* 
    //string(char* str) - 构造 
    //string(char* str) - 构造 
    //* ************************* 
    //string(char* str) - 构造 
    //string(char* str) - 构造 
    //* ************************* 
    //string(char* str) - 构造 
    //* ************************* 
    // 右值引用绑定后,右值引用的属性是左值(本身属性是左值)->要 move 一下 
    // 为什么要这样设计?为了让它能够修改! 
    return 0; 
}

最终运行结果

结尾

本文的内容到这里就全部结束了,感谢您的阅读!

目录

  1. C++11 右值引用与移动语义详解
  2. C++ 学习阶段的三个参考文档
  3. 前情提示
  4. 1 C++ 学习的三个参考文档
  5. 2 {}初始化
  6. 3 C++11 中的{}
  7. 4 引用
  8. 5 fmin
  9. 6 左值引用和右值引用
  10. 1 ~> C++11 的历史发展
  11. 1.1 历史发展
  12. 1.2 拓展讨论
  13. 2 ~> 列表初始化:{}
  14. 2.1 C++98 中的{}
  15. 2.2 C++11 中的{}
  16. 2.3 C++11 中的 std::initializer_list(初始化列表)
  17. 3 ~> 右值引用 && 移动语义
  18. 3.1 左值和右值
  19. 3.2 左值引用和右值引用
  20. 3.2.1 概念
  21. 3.2.2 左值引用、右值引用可以相互交叉
  22. 3.2.3 引用延迟生命周期
  23. 3.2.4 左值和右值的参数匹配问题
  24. 3.3 右值引用和移动语义的使用场景
  25. 3.3.1 左值引用主要使用场景
  26. 3.3.2 传值返回需要拷贝
  27. 3.3.3 移动构造和移动赋值
  28. 3.3.4 左值拷贝和右值拷贝:拷贝构造和移动构造
  29. 3.4 右值引用和移动语义解决传值返回问题
  30. 3.4.1 两种场景实践
  31. 3.4.2 传值返回已经没有拷贝了,是否意味上面的右值引用和移动语义就没意义?
  32. 3.4.3 右值对象构造,只有拷贝构造,没有移动构造的场景
  33. 3.4.4 右值对象构造,既有拷贝构造,也有移动构造的场景
  34. 3.4.5 以上两种情况结合局部对象生命周期和栈帧的角度的理解
  35. 3.4.6 右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景
  36. 3.4.7 右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景
  37. 3.5 右值引用和移动语义在传参中的提效
  38. 完整代码示例与实践演示
  39. list.h:
  40. Test.cpp:
  41. 最终运行结果
  42. 结尾

更多推荐文章

查看全部
  • C++ 模板的幻觉:实例化、重定义与隐藏依赖
  • VS Code 远程 SSH 环境下 Copilot 无法使用 Claude 模型
  • AMD 显卡 llama.cpp 兼容性与部署指南
  • 基于采样的 MPC 控制 MPPI 算法及 ROS C++ Python 仿真
  • Java 环境配置与首个程序实战:JDK + IDEA 从零搭建
  • Clawdbot(Moltbot) 飞书机器人配置教程
  • 基于 JsPDF 和 html2canvas 实现前端图表与列表数据多格式导出
  • Python 实现炸金花游戏的核心逻辑与代码示例
  • Ollama Windows 安装与使用指南:本地运行 Llama 等模型
  • Face Fusion 与 Stable Diffusion 集成方案及创意玩法解析
  • 基于 IP-Adapter 与 AnimateDiff 的 AI 动漫短剧工业化流水线
  • Android 免 Root 自动抢红包工具 AutoRobRedPackage 使用指南
  • Ubuntu 24.04 换源指南:适配新版配置文件结构
  • Flutter 三方库 arcane_helper_utils 鸿蒙化适配指南
  • ToDesk ToClaw AI 科技新闻日报自动化实战指南
  • 部署 Qwen2.5-VL-32B 踩坑实录:多卡推理 vLLM 死锁与 llama.cpp 对比
  • OpenClaw 集成 Telegram 机器人实战指南
  • 找羊开源加热台硬件与固件设计解析
  • 学术论文避坑指南:降低重复率与 AIGC 检测风险的策略
  • GitHub Copilot 模型对比与选型策略

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online