拷贝构造 vs 移动构造:从左值引用到右值引用彻底讲透(Student 示例)

很多人学 C++ 时都会卡在这几个点:

  • 左值引用和右值引用到底区别是什么?
  • 为什么拷贝构造用 const T&
  • 为什么移动构造用 T&&
  • 右值引用是不是马上销毁?
  • 为什么移动后要置 nullptr?

这篇文章用一个最简单的 Student 类,把这些问题一次讲透。

一、一个有资源的类

我们写一个最小可运行示例:

cpp 复制代码
#include <iostream>
using namespace std;

class Student {
public:
    int* age;

    Student(int a) : age(new int(a)) {
        cout << "Ctor      this=" << this
             << " age_ptr=" << age
             << " val=" << *age << endl;
    }

    // 拷贝构造(深拷贝)
    Student(const Student& other)
        : age(new int(*other.age)) {
        cout << "CopyCtor  this=" << this
             << " age_ptr=" << age
             << " val=" << *age << endl;
    }

    // 移动构造
    Student(Student&& other) noexcept
        : age(other.age) {
        other.age = nullptr;
        cout << "MoveCtor  this=" << this
             << " age_ptr=" << age << endl;
    }

    ~Student() {
        cout << "Dtor      this=" << this
             << " age_ptr=" << age << endl;
        delete age;
    }
};

这里有一个关键点:

age 是堆内存资源。

只要类里有资源,拷贝和移动问题就一定会出现。

二、为什么拷贝构造用 const T&?

cpp 复制代码
Student(const Student& other)

这是 左值引用

它能绑定:

cpp 复制代码
Student a(20);
Student b = a;  // 走拷贝构造

因为:

  • a 是左值(有名字,可寻址)
  • 左值只能绑定 T&

为什么加 const

因为:

左值通常还会继续被使用

你不能掏空它的资源。

所以必须:

cpp 复制代码
const Student&

防止误修改。

三、深拷贝在做什么?

关键语句:

cpp 复制代码
age = new int(*other.age);

拆解:

  1. other.ageint*
  2. *other.age 取出值(比如 20)
  3. new int(20) 开新内存
  4. 把新地址赋给 age

内存图:

cpp 复制代码
other.age ----> 0x1000 (20)
this->age ----> 0x2000 (20)

两份资源。

这就是深拷贝。

四、为什么移动构造用 T&&?

cpp 复制代码
Student(Student&& other)

&& 是右值引用。

它能绑定:

cpp 复制代码
Student b = Student(30);   // 临时对象
Student c = std::move(a);  // 被标记为右值

右值通常是:

  • 临时对象
  • 即将被销毁的对象
  • 或被允许"掏空"的对象

所以可以:

cpp 复制代码
age = other.age;
other.age = nullptr;

五、移动构造在做什么?

cpp 复制代码
Student(Student&& other) noexcept
    : age(other.age) {
    other.age = nullptr;
}

执行流程:

移动前:

cpp 复制代码
other.age ----> 0x1000 (20)

移动后:

cpp 复制代码
this->age  ----> 0x1000 (20)
other.age  ----> nullptr

没有分配新内存。

没有复制数据。

只是:

转移所有权

六、左值引用 vs 右值引用区别

对比项 左值引用 T& 右值引用 T&&
绑定对象 左值 右值
是否允许修改 看是否 const 允许修改
是否允许转移资源 不允许 允许
典型用途 拷贝 移动

七、重要知识点:右值引用参数在函数内部是左值

这是很多人最容易混乱的地方。

cpp 复制代码
Student(Student&& other)

虽然 other 类型是右值引用,

但在函数内部:

cpp 复制代码
other

左值

因为:

只要一个变量有名字,它就是左值。

这就是为什么:

如果你在函数内部再传递 other

必须写:

cpp 复制代码
std::move(other)

否则会走拷贝构造。

八、右值引用是不是马上销毁?

不是。

销毁由作用域决定。

例如:

cpp 复制代码
Student&& r = Student(40);

临时对象生命周期会延长到 r 结束。

所以:

右值引用 ≠ 马上销毁

右值引用 = 允许资源转移

九、拷贝 vs 移动本质区别

项目 拷贝 移动
是否分配内存
是否复制数据
是否转移所有权
性能

十、终极理解

cpp 复制代码
Student b = a;              // 拷贝
Student c = std::move(a);   // 移动
Student d = Student(50);    // 移动

拷贝:

保留原对象完整性

移动:

掏空原对象,把资源转走

总结

  1. 左值引用用于拷贝
  2. 右值引用用于移动
  3. 移动语义是资源所有权转移
  4. 引用不决定生命周期
  5. 右值引用参数在函数内部是左值
相关推荐
不想写代码的星星23 分钟前
C++ 内存管理:分区、自定义分配器、常见问题与检测工具
c++
-许平安-1 小时前
MCP项目笔记九(插件 bacio-quote)
c++·笔记·ai·plugin·mcp
沉鱼.441 小时前
第十三届题目
c语言·c++·算法
liulilittle1 小时前
C++ 无锁编程:单停多发送场景高性能方案
服务器·开发语言·c++·高性能·无锁·原子
无限进步_2 小时前
【C++】巧用静态变量与构造函数:一种非常规的求和实现
开发语言·c++·git·算法·leetcode·github·visual studio
小超超爱学习99372 小时前
大数乘法,超级简单模板
开发语言·c++·算法
xyx-3v3 小时前
qt创建新工程
开发语言·c++·qt
样例过了就是过了4 小时前
LeetCode热题100 爬楼梯
c++·算法·leetcode·动态规划
少司府4 小时前
C++基础入门:类和对象(中)
c语言·开发语言·c++·类和对象·运算符重载·默认成员函数
王老师青少年编程4 小时前
csp信奥赛c++之状压枚举
数据结构·c++·算法·csp·信奥赛·csp-s·状压枚举