Bjarne《C++之旅》Vector示例的移动赋值正确性及验证方法
C++ Vector类移动赋值运算符验证与移动操作确认方法
一、移动赋值运算符的正确实现示例
假设书中的Vector类基础结构如下,对应的移动赋值运算符可以这么写:
class Vector { private: double* elem; int sz; public: // 书中给出的移动构造函数 Vector(Vector&& a) noexcept : elem{a.elem}, sz{a.sz} { a.elem = nullptr; a.sz = 0; } // 移动赋值运算符实现 Vector& operator=(Vector&& a) noexcept { if (this != &a) { // 防自赋值,虽然场景少见但严谨性拉满 delete[] elem; // 先释放当前对象的已有资源 elem = a.elem; // 直接"窃取"源对象的内存指针 sz = a.sz; // 把源对象置成安全可析构的空状态,避免重复释放内存 a.elem = nullptr; a.sz = 0; } return *this; } // 其他必要成员(拷贝构造、拷贝赋值、析构函数) Vector(const Vector&); Vector& operator=(const Vector&); ~Vector() { delete[] elem; } };
核心要点:
- 必须加
noexcept:标准库容器依赖这个标识启用移动优化,而且移动操作本身不应该抛出异常 - 处理自赋值:避免自己给自己赋值时出现资源释放错误
- 源对象置空:移动后源对象要处于“能安全调用析构函数”的状态,不能让它还握着原来的内存指针
二、区分移动/拷贝构造函数执行的方法
1. 加日志打印最直接
在拷贝和移动构造函数里加输出语句,运行时看控制台输出就一目了然:
Vector(const Vector& a) { std::cout << "触发拷贝构造函数\n"; sz = a.sz; elem = new double[sz]; std::copy(a.elem, a.elem + sz, elem); } Vector(Vector&& a) noexcept : elem{a.elem}, sz{a.sz} { std::cout << "触发移动构造函数\n"; a.elem = nullptr; a.sz = 0; }
比如执行Vector v2 = std::move(v1);,如果打印“触发移动构造函数”,就说明走的是移动逻辑。
2. 检查源对象状态
移动操作后,源对象会被我们置为空(指针nullptr、大小0),而拷贝操作不会改变源对象。可以直接判断:
Vector v1(100); Vector v2 = std::move(v1); if (v1.elem == nullptr && v1.sz == 0) { std::cout << "确实执行了移动操作\n"; }
要是拷贝的话,v1的elem还是指向原来的内存块,sz也不会变。
3. 用编译器工具辅助
比如GCC/Clang开-Wdeprecated-copy选项,当代码本该走移动却意外触发拷贝时,编译器会给警告。或者加-fno-elide-constructors关闭返回值优化(RVO),这样能看到原本被优化掉的移动构造调用(不过RVO是合法优化,实际项目里不用特意关)。
4. 性能对比(大数据量场景)
如果Vector存了百万级别的数据,拷贝要花时间分配内存+复制数据,移动只是几个指针赋值,耗时差非常大。用std::chrono计时就能看出来:
#include <chrono> // 测试拷贝 Vector big_v(1000000); auto start = std::chrono::high_resolution_clock::now(); Vector v_copy = big_v; auto end = std::chrono::high_resolution_clock::now(); std::cout << "拷贝耗时:" << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms\n"; // 测试移动 start = std::chrono::high_resolution_clock::now(); Vector v_move = std::move(big_v); end = std::chrono::high_resolution_clock::now(); std::cout << "移动耗时:" << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms\n";
移动的耗时基本是0ms或者个位数,拷贝则会有明显的耗时。
内容的提问来源于stack exchange,提问作者heretoinfinity
相关产品推荐
相关产品推荐

