拷贝构造 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. 右值引用参数在函数内部是左值
相关推荐
张健11564096481 小时前
使用信号量限制并发数量
开发语言·c++
jc06201 小时前
6.1云原生之Docker
c++·docker·云原生
叶子野格3 小时前
《C语言学习:指针》12
c语言·开发语言·c++·学习·visual studio
Fuyo_11194 小时前
C++ 内存管理
c++·笔记
澈2075 小时前
C++面向对象:类与对象核心解析
c++·算法
6Hzlia5 小时前
【Hot 100 刷题计划】 LeetCode 141. 环形链表 | C++ 哈希表直觉解法
c++·leetcode·链表
handler016 小时前
Linux 进程探索:从 PCB 管理到 fork() 的写时拷贝
linux·c语言·c++·笔记·学习
众少成多积小致巨7 小时前
GNU Make 核心指南
android·c++
谭欣辰7 小时前
详细讲解 C++ 状压 DP
开发语言·c++·动态规划
William_wL_7 小时前
【C++】stack和queue的使用和实现(附加deque的简单介绍)
开发语言·c++