Ch 23: 智能指针
- 深入理解智能指针解决什么问题
- 掌握 unique_ptr 独占所有权的语义和用法
- 掌握 shared_ptr 共享所有权的引用计数机制
- 掌握 weak_ptr 打破循环引用和缓存场景
- 理解定制删除器和智能指针工厂
- 学会用智能指针替代裸指针
23.1 Python 引用计数 vs C++ 智能指针
Section titled “23.1 Python 引用计数 vs C++ 智能指针”Python 的垃圾回收
Section titled “Python 的垃圾回收”import sys
a = [1, 2, 3] # refcount = 1b = a # refcount = 2print(sys.getrefcount(a)) # 2(包含临时引用)
del a # refcount = 1del b # refcount = 0,对象被回收
# Python 的引用计数 + 垃圾回收器处理循环引用C++ 智能指针类型
Section titled “C++ 智能指针类型”#include <memory>#include <iostream>
// 三种智能指针std::unique_ptr<int> up; // 独占所有权,只能有一个所有者std::shared_ptr<int> sp; // 共享所有权,多个指针共享同一个对象std::weak_ptr<int> wp; // 弱引用,不增加引用计数
int main() { // unique_ptr - 独占 auto up1 = std::make_unique<int>(42); std::unique_ptr<int> up2 = std::move(up1); // 转移所有权 // up1 现在是空,up2 拥有指针
// shared_ptr - 共享 auto sp1 = std::make_shared<int>(42); std::shared_ptr<int> sp2 = sp1; // 共享,引用计数 = 2 std::cout << "Ref count: " << sp1.use_count() << "\n"; // 2
// weak_ptr - 观察但不拥有 std::weak_ptr<int> wp = sp1; std::cout << "Use count: " << wp.use_count() << "\n"; // 2(不增加)
// 检查 weak_ptr 是否有效 if (auto locked = wp.lock()) { std::cout << "Value: " << *locked << "\n"; // 42 }
return 0;}智能指针的选择原则
Section titled “智能指针的选择原则”| 场景 | 推荐指针 | 原因 |
|---|---|---|
| 单个所有者 | unique_ptr | 零开销,明确所有权 |
| 多个所有者 | shared_ptr | 引用计数 |
| 缓存/观察者 | weak_ptr | 不增加计数 |
| 禁止拷贝的资源 | unique_ptr | 独占语义 |
23.2 std::unique_ptr 详解
Section titled “23.2 std::unique_ptr 详解”创建和基本操作
Section titled “创建和基本操作”#include <memory>#include <iostream>
int main() { // 创建方式 1:make_unique(C++14) auto p1 = std::make_unique<int>(42); auto p2 = std::make_unique<int[]>(10); // 数组
// 创建方式 2:直接构造 std::unique_ptr<int> p3(new int(100));
// 解引用 std::cout << "Value: " << *p1 << "\n";
// 访问成员(如果是类指针) struct Point { int x, y; }; auto p4 = std::make_unique<Point>(Point{1, 2}); std::cout << "Point: " << p4->x << ", " << p4->y << "\n";
// 检查是否为空 if (p1) { std::cout << "p1 is not null\n"; }
// reset - 释放并可选重新指向 p1.reset(); // p1 现在是空 p1.reset(new int(50)); // p1 重新指向新对象
// release - 返回指针并释放所有权(不删除对象) int* raw = p1.release(); delete raw; // 调用者负责删除
// get - 返回原始指针(不转让所有权) p1 = std::make_unique<int>(30); int* raw2 = p1.get(); // 不要 delete! *raw2 = 100; // 可以通过原始指针修改
return 0;}独占所有权的意义
Section titled “独占所有权的意义”#include <memory>#include <iostream>
// ❌ 危险:裸指针 - 所有权不明确class Node {public: Node(int v) : value(v) {} void set_next(Node* n) { next = n; } Node* get_next() const { return next; }private: int value; Node* next;};
void bad_function(Node* ptr) { // ptr 的所有权归谁?需要删除吗?}
void badCaller() { Node* n = new Node(1); bad_function(n); // 危险!可能泄漏 delete n; // 谁知道该不该删}
// ✅ 安全:unique_ptr - 所有权明确void good_function(std::unique_ptr<Node> ptr) { // ptr 的所有权归我,函数结束自动删除}
void goodCaller() { auto n = std::make_unique<Node>(1); good_function(std::move(n)); // 转移所有权 // n 现在是空}#include <memory>#include <iostream>#include <vector>
int main() { // 移动 unique_ptr auto p1 = std::make_unique<int>(42); std::unique_ptr<int> p2 = std::move(p1); // p1 现在是空,p2 拥有指针
// ✅ 可以放入 vector std::vector<std::unique_ptr<int>> vec; vec.push_back(std::make_unique<int>(1)); vec.push_back(std::make_unique<int>(2)); vec.push_back(std::make_unique<int>(3));
// ❌ 不能直接拷贝 // vec.push_back(p2); // ❌ 编译错误!
// 移动可以 vec.push_back(std::move(p2));
// 遍历(需要 const 引用) for (const auto& up : vec) { std::cout << *up << " "; } std::cout << "\n";
// 使用 std::move 移动整个容器 std::vector<std::unique_ptr<int>> vec2; for (auto& up : vec) { vec2.push_back(std::move(up)); }
return 0;}#include <memory>#include <iostream>
int main() { // C++14 推荐:make_unique 数组 auto arr = std::make_unique<int[]>(5); for (int i = 0; i < 5; ++i) { arr[i] = i * i; }
// C++17 可以用 make_unique_for_overwrite // auto arr2 = std::make_unique_for_overwrite<int[]>(5);
// ❌ 不推荐:直接 new[] std::unique_ptr<int[]> arr3(new int[10]);
// shared_ptr 数组(C++17) // std::shared_ptr<int[]> sp_arr = std::make_shared<int[]>(10);
// 遍历 for (int i = 0; i < 5; ++i) { std::cout << arr[i] << " "; } std::cout << "\n";
return 0;}23.3 std::shared_ptr 详解
Section titled “23.3 std::shared_ptr 详解”创建和基本操作
Section titled “创建和基本操作”#include <memory>#include <iostream>
int main() { // 创建 make_shared(推荐) auto sp1 = std::make_shared<int>(42); auto sp2 = sp1; // 共享,引用计数 = 2
// 原始指针也能构造(不推荐) int* raw = new int(100); std::shared_ptr<int> sp3(raw); // 所有权转移给 sp3 // 注意:不要在原始指针还存在时删除它!
// use_count - 引用计数 std::cout << "Ref count: " << sp1.use_count() << "\n"; // 2
// unique - 是否是唯一所有者 std::cout << "sp1 unique: " << sp1.unique() << "\n"; // false
// reset - 释放当前指针 sp1.reset(); // sp1 变为空,引用计数 = 1 std::cout << "After reset, *sp2 = " << *sp2 << "\n"; // 42
// reset 并重新指向 sp2.reset(new int(99)); std::cout << "After reset2, *sp2 = " << *sp2 << "\n"; // 99
return 0;}引用计数机制
Section titled “引用计数机制”#include <memory>#include <iostream>
int main() { auto sp = std::make_shared<int>(42); std::cout << "Initial count: " << sp.use_count() << "\n"; // 1
// 拷贝构造 auto sp2 = sp; std::cout << "After copy: " << sp.use_count() << "\n"; // 2
// 作为函数参数(拷贝) void use_shared(std::shared_ptr<int>); use_shared(sp); std::cout << "After passing to function: " << sp.use_count() << "\n"; // 3
// 移动(不增加计数) auto sp3 = std::move(sp); std::cout << "After move:\n"; std::cout << " sp.use_count(): " << sp.use_count() << "\n"; // 0(sp 变为空) std::cout << " sp3.use_count(): " << sp3.use_count() << "\n"; // 3
return 0;}
void use_shared(std::shared_ptr<int> p) { std::cout << " In function: " << p.use_count() << "\n";}shared_ptr 用于工厂函数
Section titled “shared_ptr 用于工厂函数”#include <memory>#include <string>#include <iostream>
class Database {public: static std::shared_ptr<Database> create(const std::string& connection_string) { // 私有构造函数,需要通过工厂创建 return std::shared_ptr<Database>(new Database(connection_string)); }
void query(const std::string& sql) { std::cout << "Query: " << sql << "\n"; }
private: explicit Database(const std::string& conn) : connection_string_(conn) { std::cout << "Connecting to: " << conn << "\n"; }
std::string connection_string_;};
int main() { // 通过工厂创建 auto db = Database::create("postgresql://localhost");
// 可以在多个地方共享 auto db2 = db; // 引用计数 = 2
db->query("SELECT * FROM users"); db2->query("SELECT * FROM orders");
return 0;}shared_ptr 的大小
Section titled “shared_ptr 的大小”#include <memory>#include <iostream>
struct Heavy { int data[1000];};
int main() { // unique_ptr 只有一个指针大小 std::cout << "unique_ptr<int>: " << sizeof(std::unique_ptr<int>) << "\n";
// shared_ptr 两个指针大小(控制块 + 指针) std::cout << "shared_ptr<int>: " << sizeof(std::shared_ptr<int>) << "\n";
// weak_ptr 也是一个指针大小 std::cout << "weak_ptr<int>: " << sizeof(std::weak_ptr<int>) << "\n";
// 但每个 shared_ptr 指向同一个控制块 auto sp1 = std::make_shared<int>(42); auto sp2 = sp1; // 两个指针共享同一个控制块
return 0;}23.4 std::weak_ptr 详解
Section titled “23.4 std::weak_ptr 详解”#include <memory>#include <iostream>
int main() { // 创建 weak_ptr auto sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp;
// use_count - 引用计数(不增加) std::cout << "Use count: " << wp.use_count() << "\n"; // 1
// expired - 检查对象是否已删除 if (!wp.expired()) { std::cout << "Object still exists\n"; }
// lock - 获取 shared_ptr(如果对象还存在) if (auto locked = wp.lock()) { std::cout << "Value: " << *locked << "\n"; } else { std::cout << "Object expired\n"; }
// 原始 shared_ptr 销毁后,weak_ptr 过期 sp.reset(); if (wp.expired()) { std::cout << "Object expired after reset\n"; }
// lock 再次调用会失败 if (auto locked = wp.lock()) { std::cout << "Value: " << *locked << "\n"; } else { std::cout << "Cannot lock expired weak_ptr\n"; }
return 0;}打破循环引用
Section titled “打破循环引用”#include <memory>#include <iostream>
// ❌ 危险:循环引用导致内存泄漏class NodeBad {public: NodeBad(int v) : value(v) {} std::shared_ptr<NodeBad> next; int value;};
// 问题:两个 shared_ptr 互相引用,引用计数永远不为 0void bad_example() { auto n1 = std::make_shared<NodeBad>(1); auto n2 = std::make_shared<NodeBad>(2);
n1->next = n2; // n2 的引用计数 = 1 n2->next = n1; // n1 的引用计数 = 1
// 即使离开作用域,引用计数也不会到 0!}
// ✅ 正确:用 weak_ptr 打破循环class NodeGood {public: NodeGood(int v) : value(v) {} std::shared_ptr<NodeGood> next; std::weak_ptr<NodeGood> prev; // 改为 weak_ptr int value;};
void good_example() { auto n1 = std::make_shared<NodeGood>(1); auto n2 = std::make_shared<NodeGood>(2);
n1->next = n2; // n2 引用计数 = 1 n2->prev = n1; // n1 引用计数仍为 1(prev 是 weak)
// weak_ptr 不增加引用计数,所以能正常释放}
// 访问 weak_ptrvoid access_prev(const std::weak_ptr<NodeGood>& wp) { if (auto p = wp.lock()) { std::cout << "Prev value: " << p->value << "\n"; } else { std::cout << "Prev expired\n"; }}weak_ptr 用于缓存
Section titled “weak_ptr 用于缓存”#include <memory>#include <unordered_map>#include <string>#include <iostream>
template<typename Key, typename Value>class Cache {private: struct Entry { std::shared_ptr<Value> data; std::weak_ptr<Value> cache_entry; // 反向引用 }; std::unordered_map<Key, Entry> cache_; std::size_t max_size_;
public: explicit Cache(std::size_t max_size) : max_size_(max_size) {}
void put(const Key& key, std::shared_ptr<Value> value) { if (cache_.size() >= max_size_) { // 删除过期条目 cleanup_expired(); } cache_[key] = {value, value}; }
std::shared_ptr<Value> get(const Key& key) { auto it = cache_.find(key); if (it == cache_.end()) { return nullptr; }
auto sp = it->second.cache_entry.lock(); if (!sp) { cache_.erase(it); return nullptr; } return sp; }
private: void cleanup_expired() { for (auto it = cache_.begin(); it != cache_.end(); ) { if (it->second.cache_entry.expired()) { it = cache_.erase(it); } else { ++it; } } }};
struct Data { std::string content; Data(const std::string& c) : content(c) {} ~Data() { std::cout << "Data destroyed\n"; }};
int main() { Cache<int, Data> cache(3);
cache.put(1, std::make_shared<Data>("One")); cache.put(2, std::make_shared<Data>("Two")); cache.put(3, std::make_shared<Data>("Three"));
// 获取 if (auto d = cache.get(1)) { std::cout << "Data: " << d->content << "\n"; }
// 清除引用后,数据可能过期 cache.get(1).reset(); cache.get(2).reset();
// 放入新数据触发清理 cache.put(4, std::make_shared<Data>("Four"));
return 0;}23.5 智能指针作为成员
Section titled “23.5 智能指针作为成员”unique_ptr 成员
Section titled “unique_ptr 成员”#include <memory>#include <iostream>#include <string>
class Widget {public: Widget() : data_(std::make_unique<int>(0)) {} Widget(int v) : data_(std::make_unique<int>(v)) {}
int& value() { return *data_; } int value() const { return *data_; }
// 转移所有权 void set_data(std::unique_ptr<int> new_data) { data_ = std::move(new_data); }
// 释放所有权(返回) std::unique_ptr<int> release_data() { return std::move(data_); }
private: std::unique_ptr<int> data_;};
int main() { Widget w(42); std::cout << "Value: " << w.value() << "\n";
w.value() = 100; std::cout << "New value: " << w.value() << "\n";
// 转移所有权 auto new_data = std::make_unique<int>(200); w.set_data(std::move(new_data)); std::cout << "After set: " << w.value() << "\n";
// new_data 现在是空 if (!new_data) { std::cout << "new_data is now empty\n"; }
return 0;}shared_ptr 成员
Section titled “shared_ptr 成员”#include <memory>#include <iostream>#include <string>#include <vector>
class DataBuffer {public: explicit DataBuffer(std::size_t size) : data_(std::make_shared<std::vector<double>>(size)) {}
void fill(double value) { std::fill(data_->begin(), data_->end(), value); }
const auto& get_data() const { return *data_; } std::size_t size() const { return data_->size(); }
// 获取共享指针的副本(用于共享) std::shared_ptr<std::vector<double>> get_shared() const { return data_; }
private: std::shared_ptr<std::vector<double>> data_;};
int main() { DataBuffer buffer(10); buffer.fill(3.14);
// 多个地方可以共享同一个数据 auto buffer2 = buffer; // 拷贝 shared_ptr
std::cout << "Buffer size: " << buffer.size() << "\n"; std::cout << "Buffer2 shares data: " << (buffer.get_shared() == buffer2.get_shared()) << "\n";
return 0;}23.6 定制删除器
Section titled “23.6 定制删除器”unique_ptr 定制删除器
Section titled “unique_ptr 定制删除器”#include <memory>#include <iostream>#include <fstream>
// 文件删除器struct FileDeleter { void operator()(std::FILE* f) const { if (f) { std::fclose(f); std::cout << "File closed\n"; } }};
// 数组删除器struct ArrayDeleter { template<typename T> void operator()(T* p) const { delete[] p; std::cout << "Array deleted\n"; }};
int main() { // 自定义删除器 using FilePtr = std::unique_ptr<std::FILE, FileDeleter>;
FilePtr file(std::fopen("test.txt", "w")); if (file) { std::fputs("Hello", file.get()); } // 离开作用域自动关闭文件
// Lambda 删除器 auto deleter = [](int* p) { std::cout << "Deleting int: " << *p << "\n"; delete p; }; std::unique_ptr<int, decltype(deleter)> p(new int(42), deleter);
// shared_ptr 定制删除器 std::shared_ptr<int> sp(new int(100), [](int* p) { std::cout << "Custom delete: " << *p << "\n"; delete p; });
return 0;}shared_ptr 定制删除器
Section titled “shared_ptr 定制删除器”#include <memory>#include <iostream>
int main() { // shared_ptr 支持多种删除器 std::shared_ptr<int> sp1(new int(10), [](int* p) { std::cout << "Lambda delete: " << *p << "\n"; delete p; });
// shared_ptr 对数组支持不佳(C++17) // 使用 custom deleter std::shared_ptr<int[]> sp2(new int[5], [](int* p) { delete[] p; });
// 释放 sp1.reset(); sp2.reset();
// 注意:删除器类型是 shared_ptr 类型的一部分 // std::shared_ptr<int> 和 std::shared_ptr<int, Deleter> 是不同类型
return 0;}23.7 智能指针陷阱
Section titled “23.7 智能指针陷阱”#include <memory>#include <iostream>
class Bad {public: Bad() { std::cout << "Bad constructed\n"; } ~Bad() { std::cout << "Bad destroyed\n"; }};
int main() { // ❌ 错误 1:不要用同一个原始指针创建多个 shared_ptr Bad* raw = new Bad(); std::shared_ptr<Bad> sp1(raw); std::shared_ptr<Bad> sp2(raw); // ❌ 危险!引用计数会出错 // 两个 shared_ptr 各自有控制块,会导致双重删除!
// ✅ 正确:用一个 shared_ptr 拷贝构造另一个 auto sp3 = std::make_shared<Bad>(); // 或从 sp1 拷贝
// ❌ 错误 2:unique_ptr 作为参数后还使用 auto up = std::make_unique<int>(42); process(std::move(up)); // if (up) { *up; } // ❌ up 已经是空!
// ❌ 错误 3:在容器中存储 unique_ptr 后移动 std::vector<std::unique_ptr<int>> vec; vec.push_back(std::make_unique<int>(1)); auto up2 = std::move(vec[0]); // vec[0] 现在是空的!
// ✅ 正确做法:先取出再移动 auto up3 = std::move(vec[0]); // OK // 或 vec[0].reset(); // 显式清空
return 0;}正确使用模式
Section titled “正确使用模式”#include <memory>#include <iostream>
struct MyStruct { int value; MyStruct(int v) : value(v) { std::cout << "MyStruct(" << value << ")\n"; } ~MyStruct() { std::cout << "~MyStruct(" << value << ")\n"; }};
// ✅ 正确 1:工厂函数返回智能指针std::unique_ptr<MyStruct> create_struct(int v) { return std::make_unique<MyStruct>(v);}
// ✅ 正确 2:容器存储 unique_ptrvoid use_container() { std::vector<std::unique_ptr<MyStruct>> vec; vec.push_back(std::make_unique<MyStruct>(1)); vec.push_back(std::make_unique<MyStruct>(2));
for (const auto& s : vec) { std::cout << s->value << " "; } std::cout << "\n";}
// ✅ 正确 3:使用 shared_ptr 共享数据void share_data() { auto data = std::make_shared<std::vector<int>>(); data->push_back(1);
auto copy = data; // 共享引用 copy->push_back(2);
std::cout << "Size: " << data->size() << "\n"; // 2}23.8 完整示例:对象池
Section titled “23.8 完整示例:对象池”#include <memory>#include <vector>#include <stack>#include <iostream>
template<typename T>class ObjectPool {public: // 预分配对象 explicit ObjectPool(std::size_t initial_size) { pool_.reserve(initial_size); for (std::size_t i = 0; i < initial_size; ++i) { pool_.push_back(std::make_unique<T>()); } }
// 获取对象(从池中取或创建新的) std::shared_ptr<T> acquire() { std::shared_ptr<T> obj;
if (!pool_.empty()) { // 从池中取出 obj = std::shared_ptr<T>( pool_.back().release(), [this](T* p) { pool_.push_back(std::unique_ptr<T>(p)); } ); } else { // 创建新的 obj = std::shared_ptr<T>( new T(), [this](T* p) { pool_.push_back(std::unique_ptr<T>(p)); } ); }
return obj; }
std::size_t available() const { return pool_.size(); }
private: std::vector<std::unique_ptr<T>> pool_;};
struct Connection { int id; bool active = false;
Connection() : id(next_id_++) { std::cout << "Connection " << id << " created\n"; }
~Connection() { std::cout << "Connection " << id << " destroyed\n"; }
void connect() { active = true; } void disconnect() { active = false; }
private: static int next_id_;};
int Connection::next_id_ = 0;
int main() { ObjectPool<Connection> pool(3);
std::cout << "Available: " << pool.available() << "\n";
// 获取连接 auto conn1 = pool.acquire(); auto conn2 = pool.acquire(); auto conn3 = pool.acquire();
std::cout << "After acquiring 3, available: " << pool.available() << "\n";
conn1->connect(); conn1.reset(); // 归还到池 std::cout << "After returning conn1, available: " << pool.available() << "\n";
auto conn4 = pool.acquire(); std::cout << "Acquired conn4\n";
return 0;}23.9 章节总结
Section titled “23.9 章节总结”| 智能指针 | 特点 | 适用场景 |
|---|---|---|
unique_ptr | 独占所有权,零开销 | 单个所有者,不需要拷贝 |
shared_ptr | 共享所有权,引用计数 | 多个所有者共享数据 |
weak_ptr | 弱引用,不增加计数 | 打破循环引用、缓存、观察者 |
使用原则:
- 优先用
unique_ptr,需要共享时用shared_ptr - 用
make_unique和make_shared创建 - 不要用同一个原始指针创建多个 shared_ptr
- weak_ptr 用于打破循环和缓存
下章预告:ch24 学习移动语义,理解右值引用和移动构造。