拷贝构造 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.age 是 int*
  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. 右值引用参数在函数内部是左值
相关推荐
CodeYp20197 小时前
第15讲:二叉搜索树
开发语言·c++·二叉搜索树
pipixia200338 小时前
C++继承面试四连问:内存叠加、私有继承、sizeof、权限重映射
c++·面试·嵌入式·继承·面向对象·对象模型
牵猫散步的鱼儿11 小时前
C++ 内存分区
c++
choumin11 小时前
C++ 标准容器为什么不能存引用
c++·容器·引用类型
谙弆悕博士11 小时前
用 C 写了个 Windows 聊天室,踩了几个多线程和控制台的坑
c语言·开发语言·c++·windows·网络编程·聊天室
stolentime12 小时前
CF2277E Tree Guessing题解
c++·算法·贪心算法·交互·ad-hoc
syagain_zsx13 小时前
ODB 与 MySQL:C++ 里的 ORM 实践(类即表、事务铁律与查询封装)
开发语言·数据库·c++·mysql·工具库·1024程序员节
小鱼仙官13 小时前
使用Qt封装C/C++ 动态库 用于第三方调用
c语言·c++·qt
晚风予卿云月14 小时前
【C++高并发内存池】 1.定长内存池的实现
开发语言·c++·内存管理
闻缺陷则喜何志丹15 小时前
【c++20】协程与屏障
c++·算法·c++20·携程·屏障