从拷贝到移动:C++ 移动构造与移动赋值是怎么被逼出来的?(附完整示例)

很多人在学习 C++ 时,第一次看到下面这两个函数会一脸懵:

cpp 复制代码
Box(Box&& other);            // 移动构造
Box& operator=(Box&& other); // 移动赋值

这两个函数看起来像"高级语法",

但实际上它们不是凭空出现的,而是被 性能问题 + 临时对象浪费 一步步逼出来的进化结果。

本文从"问题"出发,再用一个完整可运行的例子把它讲透。

一、最初阶段:只有拷贝构造

最早 C++ 只有拷贝语义:

cpp 复制代码
Box(const Box& other);

含义就是:

cpp 复制代码
复制一份资源

示例:

cpp 复制代码
Box a(10);
Box b = a;   // 拷贝构造

如果资源很小,问题不大。

但当成员是:

  • std::string
  • std::vector
  • 大数组
  • 文件句柄
  • 网络连接

复制就会变得 慢 + 占内存

二、问题升级:临时对象的浪费

看一个常见函数:

cpp 复制代码
Box createBox() {
    Box b(10);
    return b;
}

调用:

cpp 复制代码
Box a = createBox();

如果只有拷贝构造,流程是:

cpp 复制代码
构造 b
拷贝构造 a
析构 b

问题在于:

b 马上要死了,还完整复制一份资源,纯浪费。

于是一个需求出现了:

能不能不复制,而是"搬走资源"?

三、移动语义诞生 ------ "搬家"而不是"复印"

移动语义的核心思想:

如果对象马上要被销毁,就不要复制资源,直接转移所有权。

这就引出了 移动构造移动赋值

四、完整示例代码(核心)

下面是一个完整可运行的类,包含:

  • 构造
  • 拷贝构造
  • 移动构造
  • 拷贝赋值
  • 移动赋值
  • 析构
cpp 复制代码
#include <iostream>
using namespace std;

class Box {
public:
    int* data;

    // 构造
    Box(int v) {
        data = new int(v);
        cout << "构造\n";
    }

    // 拷贝构造
    Box(const Box& other) {
        data = new int(*other.data);
        cout << "拷贝构造\n";
    }

    // 移动构造
    Box(Box&& other) {
        data = other.data;       // 接管资源
        other.data = nullptr;    // 清空对方
        cout << "移动构造\n";
    }

    // 拷贝赋值
    Box& operator=(const Box& other) {
        cout << "拷贝赋值\n";
        if (this == &other) return *this;

        delete data;
        data = new int(*other.data);
        return *this;
    }

    // 移动赋值
    Box& operator=(Box&& other) {
        cout << "移动赋值\n";
        if (this == &other) return *this;

        delete data;           // 释放旧资源
        data = other.data;     // 接管资源
        other.data = nullptr;  // 清空对方
        return *this;
    }

    ~Box() {
        delete data;
        cout << "析构\n";
    }
};

Box createBox() {
    Box b(10);
    return b;
}

int main() {
    cout << "=== 移动构造示例 ===\n";
    Box a = createBox();   // 触发移动构造(或RVO)

    cout << "=== 移动赋值示例 ===\n";
    Box b(1);
    b = createBox();       // 触发移动赋值
}

五、可能看到的输出

不同编译器略有差异,常见输出:

cpp 复制代码
=== 移动构造示例 ===
构造
移动构造
析构
析构

=== 移动赋值示例 ===
构造
构造
移动赋值
析构
析构

有时你只会看到:

cpp 复制代码
构造

那是 RVO(返回值优化) 在工作 ------

编译器直接在目标位置构造对象,连移动都省掉了。

六、移动构造 vs 移动赋值区别

类型 场景 是否已有资源 核心动作
移动构造 新对象创建 没有 直接接管资源
移动赋值 对象已存在 先释放再接管

七、为什么要把对方清空?

cpp 复制代码
other.data = nullptr;

原因:

cpp 复制代码
不清空 → 析构 double free
清空 → delete nullptr 安全

八、现实类比

cpp 复制代码
拷贝构造 = 复印整箱书
移动构造 = 把箱子搬走
拷贝赋值 = 先扔旧书再复印新书
移动赋值 = 先扔旧书再搬新箱子

九、终极锚点总结

cpp 复制代码
拷贝构造 → 复制资源
移动构造 → 搬资源给新对象
拷贝赋值 → 复制资源给已有对象
移动赋值 → 清空自己再搬资源

移动语义不是复杂语法,

它是 "拷贝太贵"被逼出来的性能进化。

当你理解:

cpp 复制代码
资源复制 vs 资源转移

移动构造和移动赋值就再也不会混了。

相关推荐
神仙别闹4 小时前
基于 C++ 实现(控制台)学生成绩管理系统
开发语言·c++
会周易的程序员4 小时前
5 节点边缘冗余方案(上):基于 aiRaft 的物联网高可用控制面设计
c++·分布式·物联网·raft·iot·共识
伞伞悦读5 小时前
【第36期】Python 目录与路径详解:pathlib、文件遍历、创建、复制、移动和删除风险
开发语言·python
qq_199886877 小时前
第6板块·第4节:构建系统与多文件项目
c++·人工智能·gpu算力
2601_966949658 小时前
只拿到股票代码还不够:量化程序到底还需要哪些标的信息?——从数据建模开始理解股票元数据
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
m0_734571768 小时前
深入理解5g <十八>传输块tbsize的计算
开发语言·5g
汉克老师9 小时前
GESP2026年9月认证C++三级( 第三部分编程题(2、分割字符串))精讲
c++·gesp·小学生·学c++编程
估值探索者9 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
RAN11426760089 小时前
1.3第一个程序的介绍
开发语言·qt
天若有情6739 小时前
C++花式魔改!手写头文件复刻Java语法,彻底打破编码思维定式
java·c++·语言做语言·jac++