公司动态
小学生学C++编程语法知识(C++移动构造函数)
一、先问一个问题什么是“移动”先别急着看代码。假设你有一个装满玩具的大箱子小明的箱子 ┌─────────────────┐ │ │ └─────────────────┘现在小红也想要这些玩具。有两种办法。方法一全部复制把小明箱子里的玩具一个一个复制一份给小红小明 ↓ 复制 小红 这样当然可以。但是如果箱子里有100万个玩具呢复制会很慢而且需要额外的空间。方法二直接把箱子交给小红小明说“我已经不要这个箱子了你直接拿走吧”于是小明的箱子 ↓ └────────→ 小红的箱子根本不用把100万个玩具重新复制一遍。这就是我们今天要学习的核心思想移动二、C为什么需要“移动”我们先看看一个比较典型的类。class Box { public: int *data; int size; Box(int n) { size n; data new int[n]; } ~Box() { delete[] data; } };假设Box a(1000000);那么a │ ├── size 1000000 │ └── data ───────→ 堆内存 ┌──────────────┐ │ 100万个数据 │ └──────────────┘现在Box b a;如果使用普通的拷贝构造函数就可能需要a │ └──────→ [100万个数据] b │ └──────→ [重新复制的100万个数据]也就是说复制一份非常大的数据。这可能非常浪费时间。三、于是C11发明了一个好东西C11提供了移动构造函数英文Move Constructor它的作用可以简单理解成“你不用复制了把资源直接交给我”四、移动构造函数长什么样最基本的形式class Box { public: int *data; int size; Box(int n) { size n; data new int[n]; } // 移动构造函数 Box(Box other) { data other.data; size other.size; other.data nullptr; other.size 0; } ~Box() { delete[] data; } };这里最重要的是Box(Box other)看到先不要害怕。这里的Box叫做右值引用我们后面可以专门学习。现在先把它简单理解成“这个对象马上就不用了可以把里面的资源搬走。”五、移动构造函数到底干了什么这是最重要的部分。假设Box a(1000000);现在a │ └──────→ 大量数据然后Box b(std::move(a));移动构造函数执行data other.data;相当于a.data ───────────┐ ↓ 大量数据 ↑ │ b.data ───────────┘注意没有复制100万个数据只是把地址交给了b。六、但是有一个非常重要的问题如果只是data other.data;那么a.data ──┐ ↓ 大数据 ↑ │ b.data ──┘现在a和b都认为自己拥有这块内存。最后会发生什么b析构delete[] data;把内存释放掉。然后a析构的时候delete[] data;又释放一次。这就出问题了七、所以移动之后要把“原来的对象”清空所以我们必须写other.data nullptr;也就是移动之前 a ─────→ 大数据 移动之后 a ─────→ nullptr b ─────→ 大数据这就非常安全了。所以移动构造函数通常有一个非常重要的动作把资源交给新对象然后把旧对象设置成“空壳”。八、用搬家来理解这个比喻非常适合小朋友。假设小明家 ┌─────────────────┐ │ ️ │ └─────────────────┘小明要搬家。普通拷贝小明把所有东西复制一份小明家 小红家 ️ ️ 复制 ─────→ 很慢。移动小明说“我不住这里了这些东西全部给你。”于是小明家 小红家 空房子 ️ 东西没有复制。只是换了一个主人。这就是移动构造函数。九、移动构造函数和拷贝构造函数有什么区别这是考试和学习中非常重要的一组概念。拷贝构造移动构造英文Copy ConstructorMove Constructor主要操作复制资源转移资源是否复制数据通常是通常不是是否更快大对象时较慢大对象时通常更快原对象保持原来的数据通常变成可安全析构的空状态参数const TT例如拷贝Box(const Box other) { // 复制数据 }意思给我复制一份。移动Box(Box other) { // 接管资源 }意思不用复制直接把你的资源给我。十、为什么要写std::move例如Box a(1000000); Box b(std::move(a));这里std::move(a)可以理解成告诉编译器“a后面我基本不需要它原来的资源了你可以把它搬走。”于是优先使用Box(Box other)移动构造函数。十一、一个完整的简单例子#include iostream #include utility using namespace std; class Box { private: int *data; int size; public: // 普通构造函数 Box(int n) { size n; data new int[n]; cout 构造函数 endl; } // 拷贝构造函数 Box(const Box other) { size other.size; data new int[size]; for (int i 0; i size; i) { data[i] other.data[i]; } cout 拷贝构造函数 endl; } // 移动构造函数 Box(Box other) { size other.size; data other.data; // 原来的对象放弃资源 other.size 0; other.data nullptr; cout 移动构造函数 endl; } // 析构函数 ~Box() { delete[] data; cout 析构函数 endl; } }; int main() { Box a(1000000); Box b(std::move(a)); return 0; }这里Box b(std::move(a));大致发生a │ └────→ 100万个数据 移动 ↓ a │ └────→ nullptr b │ └────→ 100万个数据数据没有复制。只是所有权发生了转移。十二、移动构造函数最大的作用是什么可以总结成三个字更高效特别是对象内部管理大数组 大字符串 文件资源 动态内存 大型容器的时候。例如vectorint a(10000000);如果把这么大的vector完完整整复制一份a ↓ 1000万个元素 ↓复制 b ↓ 1000万个元素很浪费。移动就可以a ↓ 数据地址 ─────────┐ │ ↓ 数据 ↑ │ b ────────────────┘直接把内部资源接过来。这也是为什么现代 C 中移动语义非常重要。十三、移动构造函数并不是“复制的高级版本”这一点一定要记住。很多初学者会认为移动构造 更快的拷贝构造。其实这个理解不够准确。它们的思想完全不同拷贝A │ └──→ 资源 ↓复制 B │ └──→ 新资源是我要一份新的。移动A │ └──→ 资源 ↓搬走 A │ └──→ 空 B │ └──→ 原来的资源是我不要了你拿去用。所以拷贝 复制移动 转让十四、什么时候会用到移动构造函数初学阶段可以先记住一种典型情况Box b(std::move(a));以及以后你会经常看到return 一个对象;例如Box createBox() { Box temp(1000000); return temp; }这里涉及创建临时对象 ↓ 把对象交给外面 ↓ 如果每次都完整复制 ↓ 可能很浪费现代 C 会通过返回值优化RVO/NRVO以及移动语义来尽可能避免不必要的复制。所以以后你看到vectorint getData() { vectorint v(1000000); return v; }不要简单理解成“把100万个元素复制出去。”现代 C 通常会尽量避免这种昂贵的复制。十五、一个非常重要的误区千万不要认为“只要有就一定会发生移动。”例如Box是右值引用类型而std::move(a)是把一个表达式转换成可以被移动的右值形式。真正是否发生移动还要看有没有匹配的移动构造函数/移动赋值运算符以及具体的重载决议。初学阶段可以先记Box b(std::move(a));→ 很可能调用移动构造函数。十六、为什么移动构造函数参数一定是你可以把Box(const Box other)理解成“这个东西还要用所以我只能复制。”而Box(Box other)理解成“这个东西马上要被处理掉了你可以把里面的资源拿走。”所以const T ↓ 我还需要你 ↓ 复制 T ↓ 我准备把你搬走 ↓ 移动这就是右值引用存在的重要意义之一。十七、最后给孩子一个“超级记忆法”以后看到Box(const Box other)就想复印机“给我复制一份。”看到Box(Box other)就想搬家公司“你不用复制东西直接搬给我。”所以对象资源 │ ┌────────┴────────┐ ↓ ↓ 拷贝 移动 │ │ ↓ ↓ 复制一份 直接搬走 │ │ ↓ ↓ 两份资源 一份资源⭐ 最后用一句话记住移动构造函数就是当一个新对象可以“接管”另一个对象的资源时不再傻傻地复制数据而是把资源直接搬过来从而提高程序效率。最核心的代码就是Box(Box other) { data other.data; // 接管资源 other.data nullptr; // 原对象放弃资源 }可以把它牢牢记成“资源搬过来旧主人变空壳。”而下节课继续学习“右值引用到底是什么为什么std::move()能让移动构造函数被调用”。把这两个知识点弄明白移动构造函数就真正理解了。