前置

在 C++ 中,右值引用(Rvalue Reference) 和移动语义(Move Semantics) 是 C++11 引入的核心特性,旨在解决「临时对象拷贝的性能开销」和「资源转移」问题,同时完善值类别体系

一、前置

首先需明确 C++ 的「值类别」—— 所有表达式可分为左值(Lvalue) 和右值(Rvalue):

类型 特征 示例
左值 有名称、可取地址、持久存在 变量(int a)、this、返回左值引用的函数
右值 无名称、不可取地址、临时存在 字面量(10,“hello”)、临时对象(string(“abc”))、返回值的函数

二、右值引用(&&):绑定右值的引用

1.定义

右值引用是一种特殊的引用类型,语法为T&&,专门用于绑定右值(临时对象、字面量等),无法绑定左值(除非用std::move转换)。

#include <iostream>
#include <string>
using namespace std;

int main() {
    // 右值引用绑定临时对象(右值)
    string&& rref = string("hello"); 
    cout << rref << endl; // 输出 hello

    string s = "world";
    // string&& rref2 = s; // 编译报错:不能绑定左值到右值引用
    string&& rref2 = move(s); // 用std::move将左值转为右值引用(将亡值)
    return 0;
}

2、核心特点

①右值引用的生命周期:绑定临时对象时,会延长临时对象的生命周期至右值引用自身销毁;
②std::move的作用:并非 “移动”,仅做「类型转换」—— 将左值强制转为右值引用(标记为 “可被移动”),对象本身未发生任何变化。

三、移动构造和移动赋值

1、使用 std::move 将左值转为右值后,是否需要手动编写移动构造 / 移动赋值函数,取决于你的类的特性和需求

类的场景 是否需要手动写移动构造 / 移动赋值 原因
仅含基本类型(int/float 等) 含动态资源(堆内存 / 文件句柄) 编译器自动生成的默认移动函数足够(移动等价于拷贝,无性能收益)
含动态资源(堆内存 / 文件句柄) 必须手动写 编译器默认移动函数是 “浅拷贝”,会导致资源重复释放、野指针等问题
依赖第三方不可拷贝资源(如 unique_ptr) 建议手动写(或编译器自动生成) 若类无自定义析构 / 拷贝函数,编译器会自动生成移动函数;否则需手动实现

2、核心原理:编译器的 “默认移动函数” 规则
C++11 起,编译器会为类自动生成默认的移动构造 / 移动赋值运算符,但满足以下全部条件时才会生成:
类没有自定义的拷贝构造函数;
类没有自定义的拷贝赋值运算符;
类没有自定义的析构函数;
类没有手动删除移动构造 / 移动赋值(如 MyClass(MyClass&&) = delete;)。
3、关键总结
std::move 只是 “触发器”:它仅将左值转为右值引用,但真正的资源转移依赖移动构造 / 移动赋值;
手动写移动函数的核心场景:类包含「动态分配的资源」(堆内存、文件句柄、网络连接等),此时编译器默认移动函数会导致资源冲突;
无需手动写的场景:类仅含基本类型 / 已实现移动语义的 STL 类型,编译器自动生成的移动函数足够安全;
避坑点:若类自定义了析构 / 拷贝构造 / 拷贝赋值,编译器会禁用默认移动函数,此时若用 std::move,会触发拷贝构造(而非移动),需手动实现移动函数。

四、移动构造和移动赋值实现

#include <iostream>
#include <cstring>
using namespace std;

class MyString {
public:
    char* buf;
    int len;

    // 1. 默认构造函数(保留,但main中不调用)
    MyString() : buf(nullptr), len(0) {
        cout << "默认构造函数" << endl;
    }

    // 2. 带参构造函数
    MyString(const char* str) {
        cout << "带参构造函数" << endl;
        len = strlen(str);
        buf = new char[len + 1];
        strcpy(buf, str);
    }

    // 3. 深拷贝构造
    MyString(const MyString& other) {
        cout << "深拷贝构造" << endl;
        len = other.len;
        buf = (other.buf == nullptr) ? nullptr : new char[len + 1];
        if (buf != nullptr) {
            strcpy(buf, other.buf);
        }
    }

    // 4. 移动构造
    MyString(MyString&& other) noexcept {
        cout << "移动构造" << endl;
        len = other.len;
        buf = other.buf;
        other.len = 0;
        other.buf = nullptr;
    }

    // 5. 移动赋值运算符
    MyString& operator=(MyString&& other) noexcept {
        cout << "移动赋值" << endl;
        if (this == &other) return *this;

        delete[] buf; // 释放当前资源
        len = other.len;
        buf = other.buf;

        other.len = 0;
        other.buf = nullptr;
        return *this;
    }

    // 6. 析构函数
    ~MyString() {
        if (buf) {
            delete[] buf;
            buf = nullptr;
        }
    }
};



int main() {
    // 删掉多余的s1(避免默认构造输出)
    // MyString s1;               

    // 2. 带参构造
    char str[] = "example";
    cout<<"----------------1----------------"<<endl;
    MyString s2(str);         
    cout<<"----------------2----------------"<<endl;
    // 3. 深拷贝构造
    MyString s3 = s2;         
    cout<<"----------------3----------------"<<endl;
    4. 移动构造(禁用RVO后触发)
    MyString s4 = getString(); 
    cout<<"----------------4----------------"<<endl;
    // 5. 移动赋值(先初始化s5,再赋值,触发operator=)
    MyString s5;              // 这里会调用默认构造(若想完全消除,可注释+调整)
    cout<<"----------------5----------------"<<endl;
    s5 = MyString("world");   // 临时对象→移动赋值(而非初始化)
    cout<<"----------------6----------------"<<endl;

    return 0;
}

注意:GCC/Clang 编译器需添加编译参数 -fno-elide-constructors(禁用返回值优化),否则移动构造会被编译器跳过。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐