拷贝构造 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. 右值引用参数在函数内部是左值
相关推荐
Hunter_pcx8 小时前
ubuntu:内存假泄漏
linux·运维·服务器·开发语言·c++·人工智能·ubuntu
Soley9 小时前
自动驾驶C++实时中间件:PuppetMaster 重构记录,阶段三:通信层抽象
c++·自动驾驶
不吃土豆的马铃薯9 小时前
5.SGI STL 二级空间配置器 _S_chunk_alloc核心函数解析
开发语言·c++·vscode·c·内存池
-快乐的程序员-9 小时前
C++的md5函数
开发语言·c++
Huangjin007_9 小时前
【C++ STL篇(九)】map容器——零基础入门与核心用法精讲
开发语言·c++·算法
十年编程老舅9 小时前
Linux NUMA架构深度剖析:内存管理、进程调度与性能优化
linux·数据库·c++·内存管理·numa
少司府9 小时前
C++基础入门:深挖list的那些事
开发语言·数据结构·c++·容器·list·类型转换·类和对象
诙_10 小时前
C++学习总结
开发语言·c++·学习
XX風10 小时前
VSCode + CMake + C++:配置文件体系完整说明
c++·ide·vscode
闻缺陷则喜何志丹10 小时前
【C++动态规划】B3734 [信息与未来 2017] 加强版密码锁|普及+
c++·算法·动态规划·洛谷