第19天:移动语义
学习目标
掌握C++11引入的移动语义,理解左值与右值的概念,学会使用移动构造函数和移动赋值运算符优化性能。
核心知识点
1. 移动语义概述
为什么需要移动语义?
- 性能优化:避免不必要的深拷贝
- 资源管理:高效转移资源所有权
- 异常安全:减少异常时的资源浪费
- 现代C++:支持现代C++编程模式
移动语义的核心思想
- 转移而非复制:将资源从一个对象转移到另一个对象
- 临时对象优化:充分利用临时对象的资源
- 零拷贝:避免不必要的资源复制
参考资料
2. 左值与右值
左值 (Lvalue)
左值是可以取地址的表达式,通常表示一个对象的内存位置。
1 2 3
| int x = 42; int& ref = x; int* ptr = &x;
|
右值 (Rvalue)
右值是不能取地址的表达式,通常是临时对象或字面量。
1 2 3 4 5 6 7 8 9
| int x = 42; std::string s = "Hello"; int result = x + 5;
std::string getString() { return "World"; } std::string str = getString();
|
左值引用与右值引用
1 2 3 4 5 6 7 8 9 10
| int x = 42;
int& lref = x;
int&& rref = 42; int&& rref2 = x + 5;
|
参考资料
3. 移动构造函数
移动构造函数用于从右值转移资源,避免深拷贝。
基本实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class MyString { private: char* data; size_t size; public: MyString(const char* str = "") : size(strlen(str)) { data = new char[size + 1]; strcpy(data, str); } MyString(const MyString& other) : size(other.size) { data = new char[size + 1]; strcpy(data, other.data); } MyString(MyString&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } ~MyString() { delete[] data; } const char* c_str() const { return data; } size_t length() const { return size; } };
|
移动构造函数要点
- noexcept:移动操作不应抛出异常
- 资源转移:将资源从源对象转移到目标对象
- 源对象重置:将源对象重置为有效但未指定状态
参考资料
4. 移动赋值运算符
移动赋值运算符用于从右值转移资源到已存在的对象。
基本实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class MyString { MyString& operator=(const MyString& other) { if (this != &other) { delete[] data; size = other.size; data = new char[size + 1]; strcpy(data, other.data); } return *this; } MyString& operator=(MyString&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } };
|
移动赋值运算符要点
- 自赋值检查:检查是否是自己赋值给自己
- 资源释放:先释放当前对象的资源
- 资源转移:转移源对象的资源
- 源对象重置:重置源对象状态
参考资料
5. std::move
std::move
用于将左值转换为右值引用,启用移动语义。
基本使用
1 2 3 4 5 6 7 8 9 10 11
| #include <utility>
std::string str1 = "Hello"; std::string str2 = "World";
str2 = std::move(str1);
std::cout << "str1: " << str1 << std::endl; std::cout << "str2: " << str2 << std::endl;
|
容器中的移动
1 2 3 4 5 6 7 8
| std::vector<std::string> vec;
std::string temp = "Hello World"; vec.push_back(std::move(temp));
std::cout << "temp: " << temp << std::endl; std::cout << "vec[0]: " << vec[0] << std::endl;
|
std::move的本质
1 2 3 4 5
| template<typename T> typename std::remove_reference<T>::type&& move(T&& t) noexcept { return static_cast<typename std::remove_reference<T>::type&&>(t); }
|
参考资料
6. 完美转发 (Perfect Forwarding)
完美转发允许函数模板将其参数完美地转发给另一个函数。
基本概念
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <utility>
template<typename T> void forwardFunction(T&& arg) { actualFunction(std::forward<T>(arg)); }
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }
|
万能引用 (Universal Reference)
1 2 3 4 5 6 7
| template<typename T> void func(T&& param);
int x = 42; func(x); func(42);
|
参考资料
7. 移动语义与容器
容器中的移动优化
1 2 3 4 5 6 7 8 9 10 11
| std::vector<std::string> vec;
vec.emplace_back("Hello"); vec.emplace_back("World");
std::string temp = std::move(vec[0]);
std::vector<std::string> vec2 = std::move(vec);
|
移动语义与算法
1 2 3 4 5
| std::vector<std::string> source = {"a", "b", "c"}; std::vector<std::string> dest;
std::move(source.begin(), source.end(), std::back_inserter(dest));
|
参考资料
8. 移动语义最佳实践
五法则 (Rule of Five)
如果类需要自定义以下任何一个,通常需要定义全部五个:
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
零法则 (Rule of Zero)
尽量使用RAII和智能指针,避免手动资源管理。
性能考虑
- 移动优于拷贝:在可能的情况下使用移动
- noexcept:移动操作应标记为noexcept
- 返回值优化:利用RVO和NRVO
参考资料
实用教程和文档
官方文档
优质教程
实战案例
今日练习
基础练习
- 实现一个资源管理类,支持移动构造和移动赋值
- 编写一个性能测试,比较拷贝和移动的性能差异
- 实现一个工厂模式,使用移动语义优化对象创建
算法题推荐
- LeetCode 146. LRU Cache - 使用移动语义优化缓存操作
- LeetCode 208. Implement Trie (Prefix Tree) - 使用移动语义优化字典树
- LeetCode 341. Flatten Nested List Iterator - 使用移动语义优化迭代器
- LeetCode 460. LFU Cache - 使用移动语义优化LFU缓存
学习要点总结
- 左值右值:理解左值和右值的区别
- 移动语义:掌握移动构造函数和移动赋值运算符
- std::move:学会使用std::move启用移动语义
- 完美转发:理解std::forward的作用
- 性能优化:通过移动语义提高程序性能
下一天预告
明天我们将学习并发编程基础,包括std::thread、同步机制、异步编程等C++11的并发特性。
上一天:Lambda表达式 | 返回第三周总览 | 下一天:并发编程基础