c++ 移动赋值/移动构造函数
在 C11 引入移动语义Move Semantics之前对象之间的赋值或初始化通常涉及深拷贝Deep Copy即复制所有数据。这对于包含动态分配资源如std::vector,std::string, 原始指针管理的内存的对象来说开销很大。移动构造函数和移动赋值运算符允许我们将资源的“所有权”从一个对象转移到另一个对象而不是复制数据。这极大地提高了性能特别是对于临时对象右值。1. 核心概念左值 vs 右值理解移动语义的前提是区分左值lvalue和右值rvalue左值有名字、有持久地址的对象如int a 10;中的a。右值通常是临时对象、字面量或即将销毁的对象如a b的结果或者函数返回的非引用对象。移动语义的核心思想是既然右值临时对象马上就要销毁了我们没必要复制它的资源直接“偷”过来用就行了。2. 移动构造函数 (Move Constructor)定义当一个新对象被一个右值初始化时调用。签名ClassName(ClassName other)参数接受一个右值引用T。作用将源对象other的资源指针直接转移给新对象并将源对象的指针置为nullptr或其他安全状态防止析构时重复释放内存。代码示例class MyVector { private: int* data; size_t size; public: // 构造函数 MyVector(size_t s) : size(s) { data new int[s]; std::cout 构造: 分配内存\n; } // 拷贝构造函数 (深拷贝) MyVector(const MyVector other) : size(other.size) { data new int[size]; std::copy(other.data, other.data size, data); std::cout 拷贝构造: 深拷贝内存\n; } // --- 移动构造函数 --- MyVector(MyVector other) noexcept : data(other.data), size(other.size) { // 关键步骤窃取指针 other.data nullptr; // 将源对象置为空防止其析构时释放这块内存 other.size 0; std::cout 移动构造: 转移所有权\n; } // 析构函数 ~MyVector() { if (data) { delete[] data; std::cout 析构: 释放内存\n; } } }; // 使用场景 MyVector createVector() { MyVector temp(100); return temp; // 返回临时对象右值 } int main() { // 这里会触发移动构造函数而不是拷贝构造 MyVector v createVector(); return 0; }3. 移动赋值运算符 (Move Assignment Operator)定义当一个已存在的对象被赋予一个右值时调用。签名ClassName operator(ClassName other)返回值通常返回*this以支持链式赋值。作用自赋值检查防止a std::move(a)导致错误。清理旧资源释放当前对象持有的旧资源。窃取新资源接管源对象的资源。重置源对象将源对象置于有效但未指定的状态通常指针置空。代码示例class MyVector { // ... (同上) ... public: // --- 移动赋值运算符 --- MyVector operator(MyVector other) noexcept { if (this ! other) { // 1. 自赋值检查 // 2. 释放当前资源 delete[] data; // 3. 窃取资源 data other.data; size other.size; // 4. 重置源对象 other.data nullptr; other.size 0; } std::cout 移动赋值: 转移所有权\n; return *this; } }; int main() { MyVector v1(10); MyVector v2(20); // std::move 将左值 v1 强制转换为右值引用触发移动赋值 v2 std::move(v1); // 此时 v1 内部指针为 nullv2 拥有了原本 v1 的内存 return 0; }4. 关键细节与最佳实践A.noexcept的重要性移动构造函数和移动赋值运算符必须标记为noexcept除非你真的可能抛出异常。原因标准库容器如std::vector在扩容重新分配内存时如果元素的移动操作是noexcept的它会优先使用移动否则为了保证异常安全如果移动中途失败原数据还在它只能退回到拷贝。如果不加noexceptstd::vectorMyVector的性能可能会退化回拷贝语义。B.std::move是什么std::move不移动任何东西。它只是一个强制类型转换工具将左值转换为右值引用T从而告诉编译器“这个对象我可以被移动请调用移动版本的操作”。调用std::move(x)后x处于有效但未指定的状态。除了销毁或重新赋值外不应再使用x的值。C. 规则之五 (Rule of Five)如果你需要自定义以下五个函数中的任何一个通常意味着你需要自定义全部五个析构函数拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符如果你只定义了移动操作而没有定义拷贝操作编译器会自动删除默认的拷贝构造和拷贝赋值函数因为资源已经被移走了默认拷贝是不安全的。D. 成员变量的自动移动如果你的类成员变量本身支持移动语义如std::unique_ptr,std::vector,std::string你可以使用default让编译器自动生成高效的移动操作class MyClass { std::vectorint vec; std::string name; // 编译器自动生成的移动构造/赋值会分别调用 vec 和 name 的移动操作 // 非常高效且安全 MyClass(MyClass) default; MyClass operator(MyClass) default; };总结对比特性拷贝构造/赋值移动构造/赋值参数类型const T(左值引用)T(右值引用)行为深度复制数据 (Deep Copy)转移资源所有权 (Steal Pointers)源对象状态保持不变变为有效但未指定状态 (通常为空)性能较慢 (涉及内存分配和复制)极快 (仅指针赋值)适用场景需要保留源对象数据时源对象是临时值或不再需要时
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2436422.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!