当前位置: 首页 > news >正文

C++笔记(面向对象)六(4+2C++11)个缺省函数详解

c++11 move 新的两个默认成员函数详解

移动构造函数

移动构造函数是一种特殊的构造函数,它通过"窃取"另一个对象(通常是临时对象)的资源来构造新对象,而不是进行昂贵的深拷贝。

语法

ClassName(ClassName&& other) noexcept;
  • 参数是 ClassName&&(右值引用)
  • 通常标记为 noexcept(不抛出异常)
  • 参数不是 const(因为我们要修改它)

为什么要移动构造函数?

先看一个没有移动构造的"痛苦"例子:
#include <iostream>
#include <cstring>
class String {
private:char* data;size_t length;
public:// 普通构造函数String(const char* str = "") {length = strlen(str);data = new char[length + 1];strcpy(data, str);std::cout << "构造: " << data << std::endl;}// 拷贝构造函数(深拷贝)String(const String& other) {length = other.length;data = new char[length + 1];strcpy(data, other.data);std::cout << "拷贝构造: " << data << std::endl;}~String() {delete[] data;std::cout << "析构: " << data << std::endl;}    const char* c_str() const { return data; }
};
String createString() {String temp("Hello World");  // 构造临时对象return temp;                 // 这里可能触发拷贝!
}
int main() {String s = createString();   // 可能有多余的拷贝std::cout << "结果: " << s.c_str() << std::endl;return 0;
}

问题临时对象 temp 在返回时可能被拷贝,然后立即被销毁,这种拷贝是浪费的!

移动构造函数来解决!

class String {
private:char* data;size_t length;
public:// ... 之前的构造函数和析构函数保持不变 ...// 移动构造函数!String(String&& other) noexcept : data(other.data), length(other.length) {  // 窃取资源// 将源对象置于有效但空的状态other.data = nullptr;other.length = 0;std::cout << "移动构造: " << data << std::endl;}
};
String createString() {String temp("Hello World");return temp;  // 现在这里会调用移动构造!
}
int main() {String s = createString();  // 调用移动构造函数,没有深拷贝!std::cout << "结果: " << s.c_str() << std::endl;return 0;
}

移动构造的工作原理

关键思想:"资源窃取"

String(String&& other) noexcept : data(other.data)     // 直接接管指针, length(other.length) // 直接接管长度
{other.data = nullptr;  // 重要!让源对象不管理资源other.length = 0;      // 源对象现在为空
}

移动 vs 拷贝的对比

String original("Hello");
// 拷贝构造:昂贵的操作
String copy = original;  // 分配新内存 + 复制数据
// original 和 copy 都有完整的数据副本
// 移动构造:高效的操作  
String moved = std::move(original);  // 只是指针转移
// moved 接管了数据,original 变为空状态

实际示例:观察移动构造的触发

#include <iostream>
#include <utility>  // for std::move
class String {// ... 同上,包含移动构造函数 ...
};
int main() {std::cout << "=== 场景1:从函数返回 ===" << std::endl;String s1 = createString();  // 移动构造std::cout << "\n=== 场景2:显式使用 std::move ===" << std::endl;String s2("Temp String");String s3 = std::move(s2);   // 移动构造// s2 现在不能再使用了!    std::cout << "\n=== 场景3:临时对象 ===" << std::endl;String s4 = String("Direct Temp");  // 可能直接构造,也可能构造+移动std::cout << "\n=== 场景4:标准库容器的好处 ===" << std::endl;std::vector<String> vec;vec.push_back(String("Vector Element"));  // 移动构造,不是拷贝!return 0;
}

移动构造函数的重点规则

1. 自动生成条件

编译器会在以下情况下自动生成移动构造函数:
  • 没有用户声明的拷贝操作(拷贝构造、拷贝赋值)
  • 没有用户声明的移动操作
  • 没有用户声明的析构函数

2. 需要自定义移动构造的情况

class ResourceHolder {int* resource;
public:// 当类管理资源时,需要自定义移动构造ResourceHolder(ResourceHolder&& other) noexcept : resource(other.resource) {other.resource = nullptr;  // 重要!}
};

3. 移动后的源对象状态

  • 移动后,源对象应该处于有效但未定义的状态
  • 通常设置为"空状态"(如 nullptr、0)
  • 移动后的对象仍然可以安全析构

4. 标记为 noexcept 的重要性

// 好的做法
String(String&& other) noexcept;
// 为什么重要?因为标准库容器(如vector)在重新分配内存时
// 如果移动构造可能抛出异常,它会选择更安全的拷贝构造

总结

移动构造函数的核心价值:
  1. 性能优化:避免不必要的深拷贝
  2. 资源转移:高效管理动态内存、文件句柄等
  3. 与现代C++特性配合:完美转发、智能指针等
  4. 零开销抽象:在需要时提供最优性能

编译器会自动优化,通常不需要你显式调用

情况1:返回值优化(RVO) - 编译器自动消除拷贝

String createString() {String temp("Hello");  // 在函数内部构造return temp;           // 返回局部对象
}
int main() {String s = createString();  // 这里可能根本没有调用移动构造!
}

在现代编译器中,这种情况会通过返回值优化(RVO) 直接在被调用的地方(main 函数中 s 的位置)构造对象,连移动构造都不需要调用

情况2:命名返回值优化(NRVO)

String createString(bool flag) {String result;if (flag) {result = "Hello";} else {result = "World";}return result;  // 命名返回值优化
}

编译器也会尝试优化,可能直接在被调用处构造 result。

那么什么时候真的会调用移动构造函数?

场景1:编译器无法进行 RVO/NRVO 时

String createString(int choice) {String a("Hello");String b("World");    if (choice == 1) {return a;  // 无法确定返回哪个,可能调用移动构造} else {return b;  // 无法确定返回哪个,可能调用移动构造}
}

场景2:显式使用 std::move

String createString() {String temp("Hello");return std::move(temp);  // 强制调用移动构造(但通常不推荐!)
}

注意:实际上,在返回语句中显式使用 std::move 反而可能阻止 RVO,所以通常不推荐这样做。

场景3:存入容器时

std::vector<String> vec;
// 这里会调用移动构造函数
vec.push_back(String("Temporary"));  // 临时对象 → 移动构造
String s("Existing");
vec.push_back(std::move(s));         // 显式移动 → 移动构造

场景4:函数参数需要移动

void process(String&& str) {  // 接收右值引用String local = std::move(str);  // 调用移动构造
}
int main() {process(String("Temp"));  // 移动构造可能在这里发生
}

正确的理解方式

移动构造函数是自动被调用的,当:
  1. 源对象是右值(临时对象、std::move 的结果)
  2. 编译器认为移动比拷贝更高效

实际开发中的建议

// 好的做法:让编译器决定
String createString() {String result("Hello");// ... 一些操作 ...return result;  // 让编译器决定:RVO 或 移动构造
}
// 不要这样做(可能阻止优化)
String createString() {String result("Hello");return std::move(result);  // 不推荐!可能阻止 RVO
}

总结

核心思想:你作为类作者,只需要提供移动构造函数。至于什么时候调用它,交给编译器和 C++ 标准来决定。
  • 你的责任:为资源管理类正确实现移动构造函数
  • 编译器的责任:在合适的时机自动调用移动构造函数
  • 标准库的责任:在容器、算法等地方充分利用移动语义
当成员函数返回临时对象时,可能需要调用移动构造,但更可能的是编译器直接优化掉(RVO),你不需要显式去调用它。

移动赋值运算符

作用:给已存在的对象高效赋值(移动语义版)
ClassName& operator=(ClassName&& other) noexcept;

核心实现(三步骤)

String& operator=(String&& other) noexcept {if (this != &other) {           // 1. 自赋值检查delete[] data;              // 2. 释放当前资源data = other.data;          // 3. 窃取资源 + 置空源对象other.data = nullptr;}return *this;
}

使用场景

String s1("Hello");
String s2("World");
s1 = std::move(s2);        // 移动赋值:s2资源转移给s1
s1 = createString();       // 临时对象自动移动赋值

与移动构造的区别

 

核心价值:避免已存在对象赋值时的深拷贝开销。
http://www.dtcms.com/a/438325.html

相关文章:

  • CTFHub 信息泄露通关笔记7:Git泄露 Log
  • 【Svelte】如何自定义路径别名(alias)?
  • 公司做哪个网站比较好西安外贸网站建设公司
  • DeepSeek-V3.2-Exp + PH8:国产大模型的性价比革命
  • 第二十三讲:特殊类和类型转换
  • 如何区分数学中的定理、引理、命题?
  • 森东网站建设南昌网站排名优化软件
  • 深圳网站建设制作开发公司开发公司app
  • 《强化学习数学原理》学习笔记8——贝尔曼最优公式小结
  • discuz网站开发深圳建设网站首页
  • Linux信号处理的相关数据结构和操作函数
  • 分类信息网站手机企业网站开发
  • 做杂志的网站有哪些织梦网站系统
  • 我的网站百度怎么搜索不到了文山网站建设代理
  • 小程序推广网站免费wordpress模板下载地址
  • 第66篇:AI+交通:智能驾驶、交通流优化与智慧物流
  • 苏州自学网站建设平台做外国美食的视频网站
  • 黄冈app下载推广平台优化视频
  • 学习日记20:GraphGPT
  • 做网站加班多吗蛋糕店网站建设方案
  • 从餐馆迎客看 accept4:更灵活的“接客“高手
  • Metasploit基础(MSF)
  • 浅析物理层过程
  • 总结 IP 协议的相关特性
  • 网球馆自动预约系统的反调试
  • PyQt5 QLineEdit组件详解:单行文本输入控件的完整指南
  • 网站建设的毕业报告公司名称变更流程及需材料
  • OSPF 多区域实验 概念及题目
  • 网站建设要经历哪些步骤丝芭传媒有限公司
  • 东莞微信网站建设怎样ceo是什么职位什么工作