以下是 C++ 移动语义、智能指针、STL容器 的完整详解,包含大量示例代码和最佳实践。
📘 第13章 拷贝控制与移动语义
13.1 拷贝控制概述
C++类可以定义以下5个特殊成员函数来控制对象的拷贝、移动、赋值和销毁:
| 拷贝构造 | ClassName(const ClassName&) | 按值传递、返回、初始化 |
| 拷贝赋值 | ClassName& operator=(const ClassName&) | 使用=赋值 |
| 移动构造 | ClassName(ClassName&&) | 从临时对象初始化 |
| 移动赋值 | ClassName& operator=(ClassName&&) | 从临时对象赋值 |
| 析构函数 | ~ClassName() | 对象销毁时 |
13.2 右值引用与移动语义
🔍 左值 vs 右值
int x = 10; // x 是左值(有名字,可取地址)
10; // 10 是右值(临时值,不可取地址)
x + 5; // 表达式结果是右值
std::move(x); // 将左值转换为右值引用
💡 右值引用 (&&)
右值引用允许我们绑定到临时对象,从而"窃取"其资源,避免深拷贝。
class String {
public:
// 普通构造函数
String(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
cout << "普通构造" << endl;
}
// 拷贝构造函数(深拷贝)
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
cout << "拷贝构造(深拷贝)" << endl;
}
// 移动构造函数(资源窃取)
String(String&& other) noexcept {
data = other.data; // 直接接管指针
other.data = nullptr; // 原对象置空
cout << "移动构造(资源窃取)" << endl;
}
// 拷贝赋值运算符
String& operator=(const String& other) {
if (this != &other) {
delete[] data;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
cout << "拷贝赋值" << endl;
return *this;
}
// 移动赋值运算符
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
cout << "移动赋值" << endl;
return *this;
}
~String() {
delete[] data;
cout << "析构" << endl;
}
private:
char* data;
};
🧪 使用示例
String createString() {
return String("Hello"); // 返回临时对象
}
int main() {
String s1("World"); // 普通构造
String s2 = s1; // 拷贝构造
String s3 = createString(); // 移动构造(C++11起)
String s4 = std::move(s1); // 显式移动
s2 = s3; // 拷贝赋值
s3 = std::move(s4); // 移动赋值
return 0;
}
输出:
普通构造
拷贝构造(深拷贝)
普通构造
移动构造(资源窃取)
普通构造
移动构造(资源窃取)
拷贝赋值
移动赋值
析构
析构
析构
析构
13.3 完美转发(Perfect Forwarding)
🔍 问题背景
在模板函数中,我们需要保持参数的左值/右值属性传递给其他函数。
💡 std::forward 解决方案
// 错误:无法保持值类别
template<typename T>
void wrapper(T arg) {
process(arg); // arg 总是左值!
}
// 正确:使用万能引用 + std::forward
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保持原始值类别
}
🧩 完整示例
#include <iostream>
#include <utility>
void process(int& x) {
std::cout << "左值引用: " << x << std::endl;
}
void process(int&& x) {
std::cout << "右值引用: " << x << std::endl;
}
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}
int main() {
int x = 10;
wrapper(x); // 调用 process(int&)
wrapper(20); // 调用 process(int&&)
wrapper(std::move(x)); // 调用 process(int&&)
return 0;
}
13.4 规则_of_三/五/零
| 三法则 | 需要定义析构、拷贝构造、拷贝赋值中的一个,通常需要全部定义 | C++98/03 |
| 五法则 | 加上移动构造和移动赋值 | C++11及以后 |
| 零法则 | 使用标准库容器和智能指针,无需自定义任何特殊成员函数 | 现代C++推荐 |
// ❌ 违反零法则
class Bad {
int* ptr;
public:
Bad() : ptr(new int(0)) {}
~Bad() { delete ptr; }
// 缺少拷贝/移动操作
};
// ✅ 遵循零法则
class Good {
std::unique_ptr<int> ptr; // 自动管理资源
public:
Good() : ptr(std::make_unique<int>(0)) {}
// 无需定义其他特殊成员函数
};
📘 第12章 智能指针
12.1 智能指针概述
| unique_ptr | 独占 | ❌ | ✅ | 零 |
| shared_ptr | 共享 | ✅ | ✅ | 引用计数 |
| weak_ptr | 弱引用 | ✅ | ✅ | 引用计数 |
12.2 unique_ptr 独占所有权
💡 基本用法
#include <memory>
#include <iostream>
class Resource {
public:
Resource() { std::cout << "Resource 构造\\n"; }
~Resource() { std::cout << "Resource 析构\\n"; }
void use() { std::cout << "使用资源\\n"; }
};
int main() {
// 创建方式1:make_unique(推荐)
std::unique_ptr<Resource> p1 = std::make_unique<Resource>();
// 创建方式2:直接构造
std::unique_ptr<Resource> p2(new Resource());
p1->use();
(*p1).use();
// 所有权转移
std::unique_ptr<Resource> p3 = std::move(p1);
// p1 现在为空,p3 拥有资源
// 重置
p3.reset(); // 释放资源
p3.reset(new Resource()); // 释放并获取新资源
// 释放但不删除
Resource* raw = p3.release();
delete raw; // 手动管理
return 0; // p2 自动析构
}
🧩 数组支持
// C++14起支持
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 42;
// 或使用数组特化
std::unique_ptr<int[], decltype(&delete[])> arr2(
new int[10], &delete[]
);
🧩 自定义删除器
auto deleter = [](FILE* f) {
if (f) fclose(f);
};
std::unique_ptr<FILE, decltype(deleter)> fp(
fopen("data.txt", "r"), deleter
);
12.3 shared_ptr 共享所有权
💡 基本用法
#include <memory>
#include <iostream>
int main() {
// 创建
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1; // 引用计数+1
std::cout << p1.use_count() << std::endl; // 2
std::cout << p2.use_count() << std::endl; // 2
// 检查唯一性
std::cout << p1.unique() << std::endl; // false
// 重置
p2.reset();
std::cout << p1.use_count() << std::endl; // 1
// 获取原始指针(谨慎使用)
int* raw = p1.get();
return 0; // 引用计数归零时自动删除
}
⚠️ 循环引用问题
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 析构\\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr; // ❌ 循环引用!
~B() { std::cout << "B 析构\\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a; // 循环引用,内存泄漏!
return 0; // a 和 b 都不会析构
}
✅ 解决方案:weak_ptr
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 析构\\n"; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // ✅ 弱引用,不增加计数
~B() { std::cout << "B 析构\\n"; }
void useA() {
if (auto sp = a_ptr.lock()) { // 提升为 shared_ptr
// 使用 sp
}
}
};
12.4 weak_ptr 弱引用
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 不增加引用计数
// 检查是否有效
if (!wp.expired()) {
std::shared_ptr<int> sp2 = wp.lock(); // 提升
std::cout << *sp2 << std::endl;
}
// 安全访问
if (auto sp2 = wp.lock()) {
std::cout << *sp2 << std::endl;
}
12.5 智能指针最佳实践
| 独占所有权 | unique_ptr |
| 共享所有权 | shared_ptr |
| 观察者/缓存 | weak_ptr |
| 工厂函数返回 | unique_ptr 或 shared_ptr |
| 类成员资源 | unique_ptr(优先) |
| 打破循环引用 | weak_ptr |
// ✅ 推荐:工厂函数
std::unique_ptr<Resource> createResource() {
return std::make_unique<Resource>();
}
// ✅ 推荐:类成员
class Widget {
std::unique_ptr<Impl> pImpl; // Pimpl惯用法
public:
Widget() : pImpl(std::make_unique<Impl>()) {}
};
📘 第9章 STL容器详解
9.1 容器分类
STL容器
│
┌──────────────┼──────────────┐
│ │ │
序列容器 关联容器 无序容器
│ │ │
┌───┼───┐ ┌───┼───┐ ┌───┼───┐
│ │ │ │ │ │ │ │ │
vector deque list map set unordered_map unordered_set
9.2 vector 动态数组
💡 基本操作
#include <vector>
#include <iostream>
int main() {
// 创建
std::vector<int> v1; // 空
std::vector<int> v2(10); // 10个元素,默认初始化
std::vector<int> v3(10, 42); // 10个元素,值为42
std::vector<int> v4 = {1, 2, 3, 4, 5}; // 初始化列表
std::vector<int> v5(v4); // 拷贝
// 容量操作
std::cout << v4.size() << std::endl; // 5
std::cout << v4.capacity() << std::endl // 可能>=5
v4.reserve(100); // 预留空间
v4.shrink_to_fit(); // 收缩容量
// 元素访问
v4[0] = 10; // 不检查边界
v4.at(0) = 20; // 检查边界,越界抛异常
v4.front(); // 第一个元素
v4.back(); // 最后一个元素
// 修改操作
v4.push_back(6); // 尾部添加
v4.pop_back(); // 尾部删除
v4.insert(v4.begin(), 0); // 插入
v4.erase(v4.begin()); // 删除
v4.clear(); // 清空
// 迭代器
for (auto it = v4.begin(); it != v4.end(); ++it) {
std::cout << *it << " ";
}
// 范围for
for (const auto& elem : v4) {
std::cout << elem << " ";
}
return 0;
}
⚠️ 迭代器失效规则
| push_back | 如果超过capacity,全部失效 |
| insert | 插入点及之后的迭代器失效 |
| erase | 被删除元素及之后的迭代器失效 |
| clear | 全部失效 |
9.3 deque 双端队列
#include <deque>
std::deque<int> d = {1, 2, 3, 4, 5};
d.push_front(0); // 前端添加
d.pop_front(); // 前端删除
d.push_back(6); // 后端添加
d.pop_back(); // 后端删除
// 支持随机访问
int x = d[0];
int y = d.at(1);
9.4 list 双向链表
#include <list>
std::list<int> lst = {1, 2, 3, 4, 5};
lst.push_front(0);
lst.push_back(6);
lst.sort(); // 排序
lst.reverse(); // 反转
lst.merge(lst2); // 合并
lst.remove(3); // 删除值为3的元素
lst.unique(); // 删除相邻重复元素
// 只支持双向迭代器
for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}
9.5 map 和 unordered_map
💡 map(有序,红黑树)
#include <map>
std::map<std::string, int> m;
// 插入
m["apple"] = 1;
m.insert({"banana", 2});
m.emplace("cherry", 3);
// 访问
int x = m["apple"]; // 不存在则创建
auto it = m.find("banana"); // 不存在返回end()
if (it != m.end()) {
std::cout << it->second;
}
// 遍历(按键排序)
for (const auto& [key, value] : m) {
std::cout << key << ": " << value << "\\n";
}
// 删除
m.erase("apple");
m.erase(m.begin());
💡 unordered_map(无序,哈希表)
#include <unordered_map>
std::unordered_map<std::string, int> um;
// 用法与 map 类似,但:
// – 平均 O(1) 查找(map 是 O(log n))
// – 元素无序
// – 需要可哈希的键类型
um.reserve(1000); // 预留桶数量
um.max_load_factor(0.75); // 设置负载因子
📊 性能对比
| 查找 | O(log n) | O(1) 平均 |
| 插入 | O(log n) | O(1) 平均 |
| 删除 | O(log n) | O(1) 平均 |
| 遍历 | 有序 | 无序 |
| 内存 | 较少 | 较多(哈希表) |
9.6 set 和 unordered_set
#include <set>
#include <unordered_set>
std::set<int> s = {5, 2, 8, 2, 1}; // {1, 2, 5, 8} 自动去重排序
std::unordered_set<int> us = {5, 2, 8, 2, 1}; // 无序去重
s.count(2); // 检查是否存在(返回1或0)
s.find(5); // 查找
s.insert(10); // 插入
s.erase(2); // 删除
9.7 容器适配器
#include <stack>
#include <queue>
// 栈
std::stack<int> st;
st.push(1);
st.pop();
st.top();
// 队列
std::queue<int> q;
q.push(1);
q.pop();
q.front();
q.back();
// 优先队列
std::priority_queue<int> pq; // 大顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> pq2; // 小顶堆
9.8 容器选择指南
| 随机访问 + 尾部操作 | vector |
| 频繁头部插入删除 | deque |
| 频繁中间插入删除 | list |
| 按键排序 + 查找 | map / set |
| 快速查找(无需排序) | unordered_map / unordered_set |
| 栈结构 | stack |
| 队列结构 | queue |
| 优先队列 | priority_queue |
🎯 综合实战案例
案例1:实现一个简单的缓存系统
#include <memory>
#include <unordered_map>
#include <list>
#include <string>
template<typename K, typename V>
class LRUCache {
public:
explicit LRUCache(size_t capacity) : cap_(capacity) {}
V get(const K& key) {
auto it = cache_.find(key);
if (it == cache_.end()) {
return V{}; // 或抛异常
}
// 移动到前端(最近使用)
items_.splice(items_.begin(), items_, it->second);
return it->second->second;
}
void put(const K& key, const V& value) {
auto it = cache_.find(key);
if (it != cache_.end()) {
items_.splice(items_.begin(), items_, it->second);
it->second->second = value;
return;
}
if (items_.size() >= cap_) {
// 删除最久未使用
auto last = items_.back();
cache_.erase(last.first);
items_.pop_back();
}
items_.push_front({key, value});
cache_[key] = items_.begin();
}
private:
size_t cap_;
std::list<std::pair<K, V>> items_;
std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> cache_;
};
案例2:资源管理器(智能指针 + 移动语义)
#include <memory>
#include <vector>
#include <iostream>
class Database {
public:
Database(const std::string& conn) : connection_(conn) {
std::cout << "连接数据库: " << connection_ << "\\n";
}
~Database() {
std::cout << "断开数据库: " << connection_ << "\\n";
}
// 禁止拷贝
Database(const Database&) = delete;
Database& operator=(const Database&) = delete;
// 允许移动
Database(Database&&) noexcept = default;
Database& operator=(Database&&) noexcept = default;
private:
std::string connection_;
};
class ResourceManager {
public:
void addResource(std::unique_ptr<Database> db) {
resources_.push_back(std::move(db));
}
Database* getResource(size_t index) {
if (index < resources_.size()) {
return resources_[index].get();
}
return nullptr;
}
private:
std::vector<std::unique_ptr<Database>> resources_;
};
📚 总结对比表
移动语义关键概念
| 左值引用 | T& | int& x = a; |
| 右值引用 | T&& | int&& r = 10; |
| 万能引用 | T&&(模板中) | template<T> void f(T&& x) |
| std::move | 转为右值引用 | std::move(x) |
| std::forward | 完美转发 | std::forward<T>(x) |
智能指针选择
是否需要共享所有权?
├── 否 → unique_ptr
└── 是 → 是否需要打破循环引用?
├── 否 → shared_ptr
└── 是 → weak_ptr(配合 shared_ptr)
容器性能对比
| vector | O(1) | O(n) | O(n) | O(1)* | O(n) |
| deque | O(1) | O(1) | O(n) | O(1) | O(n) |
| list | O(n) | O(1) | O(1) | O(1) | O(n) |
| map | O(n) | – | – | – | O(log n) |
| unordered_map | O(n) | – | – | – | O(1) |
*vector 尾插在capacity不足时需要扩容,摊还O(1)
如果你想深入了解某个特定主题(如自定义删除器、容器内存模型、STL算法等),或者需要更多实战案例,请告诉我!🚀




