一、引言
引入智能指针(Smart Pointer)是 C++ 为解决动态内存管理问题而设计的工具,其核心目的是自动管理动态分配的内存(堆内存),避免手动管理内存时常见的错误(如内存泄漏、野指针、二次释放等),提高代码的安全性和健壮性。
二、智能指针基础
2.1 具体解决的问题
手动使用new分配内存和delete释放内存时,容易出现以下问题,而智能指针通过自动化机制解决了这些问题:
内存泄漏
野指针
二次释放
2.2 RAII 模式
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 中管理资源的重要模式。智能指针是 RAII 模式的典型应用:
这种模式确保了资源的正确释放,即使在发生异常的情况下也不例外。
三、unique_ptr
3.1 定义与特性
实现独占所有权,确保同一时间只有一个智能指针管理内存,适用于单一所有者的场景,避免资源竞争。当unique_ptr被销毁时,它所指向的内存也会被自动释放。
unique_ptr的主要特性:
独占性:不允许复制操作,只能进行移动操作
轻量级:内存开销小,性能接近原始指针
可自定义删除器
3.2 实现原理
unique_ptr的实现核心在于禁用复制构造函数和复制赋值运算符,只允许移动构造和移动赋值。这确保了所有权的唯一归属。
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 33 34 35 36 37
| template<typename T, typename Deleter = std::default_delete<T>> class unique_ptr { private: T* ptr; Deleter del; // 禁用复制构造 unique_ptr(const unique_ptr&) = delete; // 禁用复制赋值 unique_ptr& operator=(const unique_ptr&) = delete; public: // 构造函数 explicit unique_ptr(T* p = nullptr) : ptr(p) {} // 移动构造函数 unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; } // 移动赋值运算符 unique_ptr& operator=(unique_ptr&& other) noexcept { if (this != &other) { del(ptr); // 释放当前资源 ptr = other.ptr; other.ptr = nullptr; } return *this; } // 析构函数 ~unique_ptr() { del(ptr); } // 其他成员函数... };
|
3.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 33 34 35 36 37 38 39 40 41
| #include <memory> #include <iostream>
class MyClass { public: MyClass() { std::cout << "MyClass constructed\n"; } ~MyClass() { std::cout << "MyClass destructed\n"; } void doSomething() { std::cout << "Doing something...\n"; } };
int main() { // 创建unique_ptr std::unique_ptr<MyClass> up1(new MyClass()); // 使用箭头运算符访问成员 up1->doSomething(); // 使用解引用运算符 (*up1).doSomething(); // 所有权转移(移动语义) std::unique_ptr<MyClass> up2 = std::move(up1); // up1现在为空 if (!up1) { std::cout << "up1 is null\n"; } // 自定义删除器示例 auto deleter = [](MyClass* p) { std::cout << "Custom deleter called\n"; delete p; }; std::unique_ptr<MyClass, decltype(deleter)> up3(new MyClass(), deleter); // 数组版本 std::unique_ptr<MyClass[]> up4(new MyClass[3]); return 0; }
|
3.4 使用场景
动态分配的单个对象或数组:作为局部变量管理动态内存
工厂函数返回值:工厂函数返回unique_ptr,明确表示调用者获得对象的唯一所有权
容器元素:作为容器元素存储动态分配的对象
Pimpl 惯用法:实现接口与实现的分离,减少编译依赖
3.5 注意事项
不能将unique_ptr直接赋值给另一个unique_ptr,必须使用std::move()
不要将原始指针交给多个unique_ptr管理,这会导致重复释放
避免将unique_ptr管理的原始指针暴露出去,以免造成悬空指针
当需要传递unique_ptr到函数时,可以通过移动语义转移所有权,或传递引用(如果不需要转移所有权)
四、shared_ptr
4.1 定义与特性
实现共享所有权,通过引用计数跟踪内存被多少个指针共享,当计数为 0 时自动释放内存,适用于多对象共享资源的场景。shared_ptr通过引用计数来管理内存:当最后一个指向该内存的shared_ptr被销毁时,内存才会被释放。
shared_ptr的主要特性:
共享性:多个shared_ptr可以指向同一对象
引用计数:内部维护引用计数,跟踪对象被引用的次数
可自定义删除器
线程安全:引用计数的操作是线程安全的
4.2 实现原理
shared_ptr的实现包含两个部分:
指向实际对象的指针
指向控制块的指针(包含引用计数、弱引用计数和删除器等)
当复制shared_ptr时,控制块中的引用计数会增加;当shared_ptr被销毁或重置时,引用计数会减少。当引用计数变为 0 时,控制块会调用删除器释放对象内存。
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 33 34 35 36 37 38 39 40 41 42 43 44
| template<typename T> class shared_ptr { private: T* ptr; // 指向对象的指针 control_block* control; // 指向控制块的指针 struct control_block { std::size_t ref_count; // 引用计数 std::size_t weak_count; // 弱引用计数 // 其他信息:删除器、分配器等 }; public: // 构造函数 explicit shared_ptr(T* p = nullptr) : ptr(p) { if (p) { control = new control_block{1, 0}; } else { control = nullptr; } } // 复制构造函数 shared_ptr(const shared_ptr& other) : ptr(other.ptr), control(other.control) { if (control) { ++control->ref_count; } } // 析构函数 ~shared_ptr() { if (control) { --control->ref_count; if (control->ref_count == 0) { delete ptr; // 释放对象 if (control->weak_count == 0) { delete control; // 当没有弱引用时,释放控制块 } } } } // 其他成员函数... };
|
4.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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include <memory> #include <iostream> #include <vector>
class MyClass { public: MyClass(int id) : id_(id) { std::cout << "MyClass " << id_ << " constructed\n"; } ~MyClass() { std::cout << "MyClass " << id_ << " destructed\n"; } void doSomething() { std::cout << "MyClass " << id_ << " doing something\n"; } private: int id_; };
int main() { // 创建shared_ptr std::shared_ptr<MyClass> sp1(new MyClass(1)); std::cout << "Reference count: " << sp1.use_count() << "\n"; // 复制shared_ptr,引用计数增加 std::shared_ptr<MyClass> sp2 = sp1; std::cout << "Reference count after copy: " << sp1.use_count() << "\n"; // 通过make_shared创建(更高效) auto sp3 = std::make_shared<MyClass>(2); // 存储在容器中 std::vector<std::shared_ptr<MyClass>> vec; vec.push_back(sp1); vec.push_back(sp2); vec.push_back(sp3); std::cout << "sp1 reference count: " << sp1.use_count() << "\n"; // 函数参数传递 auto func = [](std::shared_ptr<MyClass> sp) { std::cout << "In function, ref count: " << sp.use_count() << "\n"; }; func(sp1); // 自定义删除器 auto deleter = [](MyClass* p) { std::cout << "Custom deleter for MyClass " << p->id_ << "\n"; delete p; }; std::shared_ptr<MyClass> sp4(new MyClass(3), deleter); return 0; }
|
4.4 使用场景
共享资源:当多个对象需要共享同一个动态分配的对象时
长期存在的对象:当对象的生命周期不明确,需要多个所有者共同管理时
循环数据结构:与weak_ptr配合使用,处理可能的循环引用
跨模块资源共享:在不同模块间传递对象所有权时
4.5 注意事项
优先使用std::make_shared创建shared_ptr,而非直接使用构造函数,这样更高效且更安全
避免通过原始指针创建多个shared_ptr,这会导致引用计数不共享,造成重复释放
注意循环引用问题,这会导致内存泄漏(解决方案见weak_ptr部分)
不要用this指针初始化shared_ptr,应使用enable_shared_from_this
shared_ptr的引用计数操作是线程安全的,但对所指向对象的访问需要额外同步
五、weak_ptr
5.1 定义与特性
配合shared_ptr使用,解决循环引用问题(两个shared_ptr相互引用导致引用计数无法归零的内存泄漏),它不增加引用计数,仅作为 “弱引用” 观察资源。
weak_ptr的主要特性:
5.2 实现原理
weak_ptr指向shared_ptr的控制块,它维护了一个弱引用计数。当shared_ptr的引用计数变为 0 时,对象会被销毁,但控制块会保留直到弱引用计数也变为 0。
weak_ptr不能直接访问对象,必须通过lock()方法获取一个shared_ptr,该方法在对象仍然存在时返回一个有效的shared_ptr,否则返回空shared_ptr。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <memory> #include <iostream>
class B; // 前向声明
class A { public: std::shared_ptr<B> b_ptr; ~A() { std::cout << "A destructed\n"; } };
class B { public: // 使用weak_ptr打破循环引用 std::weak_ptr<A> a_ptr; ~B() { std::cout << "B destructed\n"; } };
int main() { // 创建循环引用场景 auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a; // 此时a和b的引用计数都是1 std::cout << "a use count: " << a.use_count() << "\n"; std::cout << "b use count: " << b.use_count() << "\n"; // 通过weak_ptr访问对象 if (auto locked = b->a_ptr.lock()) { std::cout << "Successfully locked a from b\n"; std::cout << "Locked a use count: " << locked.use_count() << "\n"; } else { std::cout << "a has been destroyed\n"; } // weak_ptr的过期检查 std::weak_ptr<A> weak_a = a; a.reset(); // 重置a,引用计数减为0 if (weak_a.expired()) { std::cout << "weak_a has expired\n"; } else { std::cout << "weak_a is still valid\n"; } return 0; }
|
5.4 使用场景
打破循环引用:当两个或多个shared_ptr形成循环引用时,使用weak_ptr打破循环
缓存机制:临时引用对象,不希望影响对象的生命周期
观察者模式:观察者可以通过weak_ptr观察被观察者,而不影响被观察者的生命周期
避免悬空指针:需要检查对象是否仍然存在时
5.5 注意事项
weak_ptr不能直接访问对象,必须通过lock()方法获取shared_ptr后才能访问
在使用lock()返回的shared_ptr之前,应检查其有效性
weak_ptr的expired()方法可以检查对象是否已被销毁
不要存储lock()返回的shared_ptr,这会无意中延长对象的生命周期
六、auto_ptr(已弃用)
6.1 定义与问题
auto_ptr是 C++98 标准中引入的智能指针,旨在解决手动内存管理的问题。然而,由于其设计缺陷,在 C++11 标准中已被弃用,并在 C++17 中被正式移除。
auto_ptr的主要问题:
复制语义不合理:复制auto_ptr会转移所有权,这与直觉不符
不能用于容器:由于复制语义的问题,auto_ptr不能安全地存储在标准容器中
不支持数组:auto_ptr不能管理动态分配的数组
缺乏自定义删除器支持
6.2 使用示例(不推荐)
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
| #include <memory> #include <iostream>
class MyClass { public: MyClass(int id) : id_(id) { std::cout << "MyClass " << id_ << " constructed\n"; } ~MyClass() { std::cout << "MyClass " << id_ << " destructed\n"; } private: int id_; };
int main() { std::auto_ptr<MyClass> ap1(new MyClass(1)); // 复制auto_ptr会转移所有权 std::auto_ptr<MyClass> ap2 = ap1; // ap1现在变为空,访问会导致未定义行为 // ap1->someMethod(); // 危险! return 0; }
|
6.3 替代方案
auto_ptr的所有使用场景都可以被其他智能指针替代:
独占所有权场景:使用unique_ptr替代
共享所有权场景:使用shared_ptr替代
数组管理:使用unique_ptr<T[]>替代
七、智能指针对比分析
特性 |
unique_ptr |
shared_ptr |
weak_ptr |
auto_ptr(已弃用) |
所有权 |
独占 |
共享 |
无 |
独占(复制转移) |
复制操作 |
禁止 |
允许(引用计数 + 1) |
允许 |
允许(转移所有权) |
移动操作 |
允许 |
允许 |
允许 |
不支持(C++11 前) |
内存开销 |
小(与原始指针相当) |
中(额外控制块) |
中(指向控制块) |
小 |
引用计数 |
无 |
有 |
无(观察引用计数) |
无 |
线程安全 |
无(指针本身) |
引用计数线程安全 |
无 |
无 |
数组支持 |
有(unique_ptr<T []>) |
无(需自定义删除器) |
无 |
无 |
循环引用 |
不涉及 |
可能产生 |
解决循环引用 |
不涉及 |
自定义删除器 |
支持 |
支持 |
|
|