C++11中的移动语义

发布于:2025-08-10 ⋅ 阅读:(30) ⋅ 点赞:(0)

一、核心概念

  • 左值与右值

    • 左值(Lvalue):可以取地址的对象(有名字的变量),如int a = 5;中的a
    • 右值(Rvalue):临时对象或字面量(无名字),如5、表达式a + b的结果、函数返回的临时对象。
  • 移动语义
    当操作右值(临时对象)时,不复制其资源(如堆内存),而是直接 “窃取” 资源的所有权,避免不必要的内存分配 / 释放,提升效率。

二、移动构造函数

1. 定义

移动构造函数是一种特殊的构造函数,参数为当前类的右值引用,用于从右值对象 “移动” 资源到新对象。

class MyString {
private:
    char* data;
    int length;

public:
    // 移动构造函数(参数为右值引用)
    MyString(MyString&& other) noexcept 
        : data(other.data), length(other.length) {
        // 标记原对象资源为“空”,避免析构时重复释放
        other.data = nullptr;
        other.length = 0;
    }
};
2. 特点
  • 参数必须是右值引用(&&),且通常不应该是const(因为需要修改原对象)。
  • 通常用noexcept修饰(告诉编译器此函数不会抛出异常,便于容器优化)。
  • 作用:接管右值对象的资源(如堆内存、文件句柄),原对象会被置为 “空状态”(避免析构冲突)。

三、移动赋值运算符

1. 定义

移动赋值运算符用于将一个右值对象的资源 “移动” 给已存在的对象,类似于移动构造,但针对的是赋值操作。

class MyString {
public:
    // 移动赋值运算符
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {  // 避免自赋值
            // 释放当前对象的资源
            delete[] data;
            
            // 接管右值对象的资源
            data = other.data;
            length = other.length;
            
            // 标记原对象资源为“空”
            other.data = nullptr;
            other.length = 0;
        }
        return *this;
    }
};
2. 特点
  • 与移动构造类似,参数为右值引用,通常用noexcept修饰。
  • 需先释放当前对象的资源,再接管右值对象的资源,最后处理原对象。

四、与拷贝构造 / 赋值的区别

操作 拷贝构造 / 赋值 移动构造 / 赋值
参数 常量左值引用(const T& 右值引用(T&&
行为 复制资源(深拷贝) 转移资源所有权(不复制)
原对象状态 保持不变 被修改为 “空状态”(如指针置空)
适用场景 左值对象(有名字的变量) 右值对象(临时对象)

五、问题

  1. 何时会调用移动构造 / 赋值?

    • 当实参是右值时(如临时对象、std::move转换的左值)。
      例:
    MyString a;
    MyString b = std::move(a);  // 调用移动构造(a被转为右值)
    b = MyString("temp");       // 调用移动赋值(临时对象是右值)
    
  2. std::move的作用?

    • 并非 “移动” 资源,而是将左值强制转换为右值引用,让编译器优先选择移动语义。
    • 注意:std::move后原对象可能变为 “空状态”,不应再使用(除非重新赋值)。
  3. 默认移动函数的生成规则?

    • 若用户未定义任何拷贝构造、拷贝赋值、移动构造、移动赋值或析构函数,编译器会自动生成默认移动函数。
    • 若定义了拷贝操作(拷贝构造 / 赋值),编译器不会自动生成移动函数(需手动定义)。
    • 若定义了移动函数,默认拷贝函数会被删除(需显式定义才可用)。
  4. 为什么移动函数通常用noexcept

    • 标准容器(如vector)在扩容时,若元素的移动构造是noexcept,会使用移动语义;否则会使用拷贝(避免移动中抛异常导致数据丢失)。
    • 加上noexcept可提升容器操作的效率。
  5. 移动语义的优势?

    • 避免临时对象的深拷贝,减少内存分配 / 释放开销(尤其对大对象,如字符串、容器)。
    • 例:函数返回大对象时,编译器可通过移动语义避免拷贝(返回值优化 RVO 的补充)。
  6. 如何禁用移动语义?

    • 显式删除移动函数:MyString(MyString&&) = delete;
    • 或定义拷贝函数(编译器不再生成默认移动函数)。

六、经典场景示例

#include <iostream>
#include <utility> // for std::move

class Resource {
private:
    int* data;
public:
    Resource() : data(new int[1000]) { 
        std::cout << "构造函数:分配资源\n"; 
    }

    // 移动构造
    Resource(Resource&& other) noexcept : data(other.data) {
        other.data = nullptr;
        std::cout << "移动构造:转移资源\n";
    }

    // 移动赋值
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
            std::cout << "移动赋值:转移资源\n";
        }
        return *this;
    }

    ~Resource() {
        if (data) delete[] data;
        std::cout << "析构函数:释放资源\n";
    }
};

int main() {
    Resource a;               // 构造:分配资源
    Resource b = std::move(a); // 移动构造:b接管a的资源(a变为空)
    Resource c;
    c = std::move(b);         // 移动赋值:c接管b的资源(b变为空)
    return 0;
}

输出:

构造函数:分配资源
移动构造:转移资源
构造函数:分配资源
移动赋值:转移资源
析构函数:释放资源
析构函数:释放资源
析构函数:释放资源


网站公告

今日签到

点亮在社区的每一天
去签到