shared_ptr
概述
C++
标准库中的一种智能指针,用于自动管理动态分配的对象的生命周期- 主要机制是通过引用计数(
Reference Counting
)来追踪有多少个shared_ptr
实例共享管理同一个对象- 当引用计数降为零时,对象会被自动释放
内部组成
- 指向管理对象的指针
- 用于直接访问实际的资源对象
- 控制块
- 控制块存储了引用计数等重要信息,用于管理对象的生命周期
控制块
- 引用计数
- 跟踪有多少个
shared_ptr
指向该对象
- 跟踪有多少个
- 弱引用计数
- 用于追踪有多少个
weak_ptr
引用控制块 - 即使
use_count
变为0
,控制块依然存在,直到weak_count
也降为0
时才释放
- 用于追踪有多少个
- 管理对象的指针
- 指向实际的对象,
shared_ptr
通过它访问对象
- 指向实际的对象,
- 自定义删除器
- 可选的,用户定义的资源释放方式,如使用
std::free
、delete
、自定义函数等
- 可选的,用户定义的资源释放方式,如使用
控制块生命周期
- 控制块的生命周期与弱引用计数相关,只有当
weak_count
也降为0
时,控制块才会被释放 - 这允许
weak_ptr
在共享对象销毁后仍能检测对象是否已经被销毁
引用计数管理
- 加引用计数:
- 当
shared_ptr
被复制构造或赋值时,use_count
增加
- 当
- 减少引用计数:
- 当
shared_ptr
被销毁、赋值或重置时,use_count
减少
- 当
- 对象释放:
- 当
use_count
降为0
时,shared_ptr
会调用Deleter
释放对象 - 此时,
use_count
为0,如果weak_count
也为0
,也就是没有weak_ptr
对象指向shared_ptr
,控制器才会被释放
- 当
线程安全
- 引用计数的增加和减少是线程安全的
- 对象的删除操作由引用计数降为零的线程执行,本身是线程安全的
- 但析构函数内部的操作需要手动保证线程安全
enable_shared_from_this
- 概述
- 用来解决对象内部获取其
shared_ptr
的工具,它允许类从内部获得指向自己的shared_ptr
,并确保不会创建新的控制块
- 用来解决对象内部获取其
- 理解
MyClass
的某个对象对应的所有shared_ptr
对象,共用了同一个控制器MyClass
析构函数还是需要手动确保线程安全
- 好处
- 避免资源浪费
- 确保引用计数同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <memory> #include <iostream> class MyClass : public std::enable_shared_from_this<MyClass> { public: std::shared_ptr<MyClass> get_shared() { return shared_from_this(); } ~MyClass() { std::cout << "MyClass destroyed\n"; } }; int main() { std::shared_ptr<MyClass> obj = std::make_shared<MyClass>(); std::shared_ptr<MyClass> another = obj->get_shared(); // 从内部获取 shared_ptr return 0; } |
转为shared_ptr
- 可以通过
std::move
将unique_ptr
转换为shared_ptr
- 但反之不可
1 2 3 4 5 6 7 8 |
#include <memory> #include <iostream> int main() { std::unique_ptr<int> uptr = std::make_unique<int>(10); std::shared_ptr<int> sptr = std::move(uptr); // 转换为 shared_ptr return 0; } |
引用循环
- 概述
shared_ptr
的引用循环(Reference Cycle
)是一个常见的内存管理问题,尤其是在使用智能指针管理对象时- 这种情况会导致内存泄漏,因为涉及到的对象之间相互持有
shared_ptr
,使得引用计数永远无法降为零,从而导致对象无法被正确释放
- 产生
- 引用循环通常发生在两个或多个对象相互持有
shared_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 |
#include <memory> #include <iostream> class B; // 前向声明 class A { public: std::shared_ptr<B> ptrB; // A 持有 B 的 shared_ptr ~A() { std::cout << "A destroyed" << std::endl; } }; class B { public: std::shared_ptr<A> ptrA; // B 持有 A 的 shared_ptr ~B() { std::cout << "B destroyed" << std::endl; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->ptrB = b; // A 持有 B b->ptrA = a; // B 持有 A // main 函数结束时,a 和 b 离开作用域,但不会释放 A 和 B return 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 |
#include <memory> #include <iostream> class B; // 前向声明 class A { public: std::shared_ptr<B> ptrB; // A 持有 B 的 shared_ptr ~A() { std::cout << "A destroyed" << std::endl; } }; class B { public: std::weak_ptr<A> ptrA; // B 持有 A 的 weak_ptr ~B() { std::cout << "B destroyed" << std::endl; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->ptrB = b; // A 持有 B b->ptrA = a; // B 持有 A(使用 weak_ptr) // main 函数结束时,a 和 b 离开作用域,A 和 B 的析构函数将被正确调用 return 0; } |
- 面对多个相互引用的类时:
- 至少保证在引用链中存在一个
weak_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 |
#include <memory> #include <iostream> class B; // 前向声明 class C; class A { public: std::shared_ptr<B> ptrB; // A 持有 B 的 shared_ptr ~A() { std::cout << "A destroyed" << std::endl; } }; class B { public: std::shared_ptr<C> ptrC; // B 持有 C 的 shared_ptr ~B() { std::cout << "B destroyed" << std::endl; } }; class C { public: std::weak_ptr<A> ptrA; // C 持有 A 的 weak_ptr,打破循环 ~C() { std::cout << "C destroyed" << std::endl; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); auto c = std::make_shared<C>(); a->ptrB = b; // A 持有 B b->ptrC = c; // B 持有 C c->ptrA = a; // C 持有 A (使用 weak_ptr) // main 函数结束时,a、b、c 离开作用域,所有对象的析构函数将被正确调用 return 0; } |
引用循环-观察者模式
- 使用
shared_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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#include <iostream> #include <vector> #include <memory> #include <algorithm> // 观察者接口 class Observer { public: virtual void update() = 0; virtual ~Observer() { std::cout << "Observer destroyed\n"; } }; // 被观察者类 class Subject { public: void addObserver(const std::shared_ptr<Observer>& observer) { // 引用计数从1变为2 observers.push_back(observer); // 被观察者持有观察者的 shared_ptr } void removeObserver(const std::shared_ptr<Observer>& observer) { observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); } void notifyObservers() { for (const auto& observer : observers) { observer->update(); // 安全调用 update,因为持有的是 shared_ptr } } ~Subject() { std::cout << "Subject destroyed\n"; } private: std::vector<std::shared_ptr<Observer>> observers; // 使用 shared_ptr 持有观察者 }; // 具体观察者类 class ConcreteObserver : public Observer { public: ConcreteObserver(const std::string& name) : name(name) {} void update() override { std::cout << name << " received update notification\n"; } ~ConcreteObserver() { std::cout << name << " destroyed\n"; } private: std::string name; }; int main() { auto subject = std::make_shared<Subject>(); auto observer1 = std::make_shared<ConcreteObserver>("Observer1"); // 引用计数从0变为1 auto observer2 = std::make_shared<ConcreteObserver>("Observer2"); subject->addObserver(observer1); subject->addObserver(observer2); // 通知观察者 subject->notifyObservers(); // 销毁一个观察者 // 引用计数从2变为1 observer1.reset(); // 再次通知,观察者列表中 observer1 仍然存在,因为 Subject 持有它的 shared_ptr subject->notifyObservers(); // 主函数结束时,循环引用导致 Subject 和 Observer 未被销毁,造成内存泄漏 return 0; // 程序结束,由于观察者引用计数不是0,所以没有被释放,内存泄漏 } |
- 使用
weak_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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
#include <iostream> #include <vector> #include <memory> #include <algorithm> // 观察者接口 class Observer { public: virtual void update() = 0; virtual ~Observer() { std::cout << "Observer destroyed\n"; } }; // 被观察者类 class Subject { public: void addObserver(const std::shared_ptr<Observer>& observer) { observers.push_back(observer); // 被观察者持有观察者的 weak_ptr } void removeObserver(const std::shared_ptr<Observer>& observer) { observers.erase(std::remove_if(observers.begin(), observers.end(), [&observer](const std::weak_ptr<Observer>& weak_observer) { auto obs = weak_observer.lock(); return obs == observer; }), observers.end()); } void notifyObservers() { // 通知所有观察者 for (auto it = observers.begin(); it != observers.end(); ) { if (auto obs = it->lock()) { // 通过 weak_ptr 获取 shared_ptr obs->update(); ++it; } else { // 观察者已被销毁,从列表中移除 it = observers.erase(it); } } } ~Subject() { std::cout << "Subject destroyed\n"; } private: std::vector<std::weak_ptr<Observer>> observers; // 使用 weak_ptr 持有观察者 }; // 具体观察者类 class ConcreteObserver : public Observer { public: ConcreteObserver(const std::string& name) : name(name) {} void update() override { std::cout << name << " received update notification\n"; } ~ConcreteObserver() { std::cout << name << " destroyed\n"; } private: std::string name; }; int main() { auto subject = std::make_shared<Subject>(); auto observer1 = std::make_shared<ConcreteObserver>("Observer1"); auto observer2 = std::make_shared<ConcreteObserver>("Observer2"); subject->addObserver(observer1); subject->addObserver(observer2); // 通知观察者 subject->notifyObservers(); // 销毁一个观察者 observer1.reset(); // 再次通知,验证 observer1 已经被移除 subject->notifyObservers(); // 主函数结束时,所有对象将被正确销毁 return 0; } |
理解
- 为什么析构函数内部的操作需要手动保证线程安全
- 当
shared_ptr
的引用计数在线程X
中变为0
时,它立即开始释放对象,调用对象的析构函数 - 此时其他的线程比如
Y
可能引用计数不为0
,它可能正在操作或访问这个对象 - 于是就会出现访问已释放的对象的风险,导致未定义行为
- 当
weak_ptr
概述
C++11
引入的一种智能指针,专为解决循环引用问题而设计- 它是对
std::shared_ptr
的一种弱引用,避免了shared_ptr
间的循环引用,能够有效防止内存泄漏 weak_ptr
不参与引用计数的增加,因此不会影响对象的生命周期
内部组成
- 控制块
weak_ptr
的核心在于与shared_ptr
共享同一个控制块- 控制块是
shared_ptr
和weak_ptr
共同管理对象生命周期的关键数据结构
- 指向控制块的指针
weak_ptr
自身包含一个指向控制块的指针,而不直接包含指向对象的指针- 通过指向控制块,
weak_ptr
可以间接地了解对象的状态(是否被销毁),而不直接影响对象的生命周期
- 独立于对象的弱引用计数
weak_count
用于跟踪有多少个weak_ptr
引用控制块- 即使
shared_ptr
的use_count
变为 0,控制块也不会立即销毁 - 只有当
weak_count
也变为 0 时,控制块才会被销毁
这种设计保证了weak_ptr
可以安全地检查对象是否已经被销毁,即使没有任何shared_ptr
存在
控制块
weak_ptr
和对应的shared_ptr
使用的是同一个控制块- 引用计数
- 跟踪有多少个
shared_ptr
指向该对象
- 跟踪有多少个
- 弱引用计数
- 用于追踪有多少个
weak_ptr
引用控制块
- 用于追踪有多少个
- 管理对象的指针
- 指向实际的对象,
shared_ptr
通过它访问对象
- 指向实际的对象,
- 自定义删除器
- 可选的,用户定义的资源释放方式,如使用
std::free
、delete
、自定义函数等
- 可选的,用户定义的资源释放方式,如使用
线程安全
- 引用计数的增加和减少是线程安全的
expired()
方法是线程安全的lock()
方法是线程安全的- 直接访问
weak_ptr
本身的状态并非线程安全- 比如两个线程同时对同一个
weak_ptr
调用reset()
、赋值、或修改等操作,
这些并不是线程安全的,需要额外的同步机制(如互斥锁)来确保安全 - 当两个线程同时操作同一个
weak_ptr
时,可能导致一个线程正在析构对象,而另一个线程还在试图访问对象,这种情况就可能导致访问已释放的内存 - 例如,一个线程通过
weak_ptr
的lock()
获取了一个shared_ptr
,而此时另一个线程可能正在销毁这个对象的资源,这种情况可能导致未定义行为
- 比如两个线程同时对同一个
工作原理
- 创建
weak_ptr
1 2 |
std::shared_ptr<int> sp = std::make_shared<int>(10); std::weak_ptr<int> wp = sp; // weak_count 增加 1,use_count 不变 |
- 访问控制块
weak_ptr
可以通过控制块检查对象是否已被销毁,通过expired()
方法检测use_count
是否为 0weak_ptr
使用lock()
方法可以获取一个新的shared_ptr
,如果use_count
为 0,则lock()
返回空的shared_ptr
- 销毁控制块
- 当
shared_ptr
的use_count
降为0
时,控制块中的对象会被销毁,但控制块本身会继续存在,直到weak_count
也降为0
- 当所有
weak_ptr
也被销毁或reset()
后,weak_count
变为0
,控制块才会被释放
- 当
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> #include <memory> int main() { std::shared_ptr<int> sp = std::make_shared<int>(42); // 创建对象和控制块,use_count = 1, weak_count = 0 std::weak_ptr<int> wp = sp; // weak_count 增加到 1, use_count 不变 std::cout << "use_count: " << sp.use_count() << std::endl; // 输出 1 std::cout << "weak_count: (inferred from wp.lock) " << (wp.lock() ? 1 : 0) << std::endl; sp.reset(); // 销毁 shared_ptr,use_count 降为 0,weak_count 仍为 1 std::cout << "After sp.reset():" << std::endl; std::cout << "use_count: " << wp.use_count() << std::endl; // 输出 0,因为对象已销毁 std::cout << "Is wp expired? " << (wp.expired() ? "Yes" : "No") << std::endl; // 输出 Yes wp.reset(); // weak_ptr 也被销毁,此时 weak_count 变为 0,控制块被释放 return 0; } |
与 shared_ptr
的关系
- 控制块的共享
- 你创建一个
shared_ptr
时,会创建一个控制块
控制块中包含了use_count
(引用计数)和weak_count
(弱引用计数),以及对实际对象的指针和删除器 - 当从
shared_ptr
创建一个weak_ptr
时,weak_ptr
并不会直接持有对对象的指针或shared_ptr
,而是通过控制块感知对象的状态 weak_ptr
增加的是控制块的weak_count
,不会影响shared_ptr
的use_count
- 你创建一个
weak_ptr
保存的是控制块的指针weak_ptr
内部保存了一个指向shared_ptr
所使用的控制块的指针- 通过这个控制块,
weak_ptr
可以了解对象是否存在,并在需要时通过lock()
创建一个新的shared_ptr
来安全访问对象
与 shared_ptr
的区别
shared_ptr
直接持有对象的指针,并增加引用计数来管理对象的生命周期weak_ptr
不直接持有对象的指针,而是依赖于控制块中的引用计数来判断对象的状态
weak_ptr
不能直接访问对象- 而是需要通过
lock()
获取一个新的shared_ptr
来安全地访问对象
- 而是需要通过
expired
- 用于检查
weak_ptr
所指向的对象是否已经被销毁 - 返回
true
:表示对象已经被销毁,shared_ptr
的引用计数为0
- 返回
false
:表示对象仍然存在,shared_ptr
的引用计数大于0
1 2 3 4 5 6 7 8 |
std::shared_ptr<int> sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; std::cout << "Is expired? " << (wp.expired() ? "Yes" : "No") << std::endl; // 输出 No sp.reset(); // 销毁 shared_ptr std::cout << "Is expired? " << (wp.expired() ? "Yes" : "No") << std::endl; // 输出 Yes |
lock
lock()
方法返回一个新的shared_ptr
,指向weak_ptr
所观测的对象- 如果对象还存在(即
shared_ptr
的引用计数use_count
大于0
),lock()
返回一个指向对象的shared_ptr
,这意味着对象还可以被安全使用 - 如果对象已被销毁(即
shared_ptr
的引用计数use_count
为0
),lock()
返回一个空的shared_ptr
,这表示对象已经被释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
std::shared_ptr<int> sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; if (auto locked = wp.lock()) { std::cout << "Object is alive, value: " << *locked << std::endl; // 输出对象值 } else { std::cout << "Object has been destroyed." << std::endl; } sp.reset(); // 销毁 shared_ptr if (auto locked = wp.lock()) { std::cout << "Object is alive." << std::endl; } else { std::cout << "Object has been destroyed." << std::endl; // 对象已销毁 } |
场景
- 解决循环引用问题
- 常用于
Observer
模式、父子关系、双向关联的类中,防止相互持有导致的循环引用
- 常用于
- 缓存与共享资源的管理
weak_ptr
常用于缓存系统中,用来跟踪某些对象是否仍在使用,而不强制延长对象的生命周期
- 安全监测对象的销毁
weak_ptr
允许在对象已销毁时,避免非法访问,通过lock()
的返回值检查对象是否依然存在
unique_ptr
概述
C++11
引入的智能指针,用于管理动态分配的对象,确保对象的生命周期和内存管理更加安全和简洁- 与其他智能指针不同,
unique_ptr
是独占所有权的指针,即一个unique_ptr
对象拥有其所指向的对象,且不能与其他unique_ptr
共享所有权
用法
- 创建
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <memory> int main() { std::unique_ptr<int> ptr1 = std::make_unique<int>(10); // 推荐使用 make_unique 创建 std::unique_ptr<int> ptr2(new int(20)); // 直接通过 new 创建(不推荐) std::cout << "ptr1: " << *ptr1 << std::endl; std::cout << "ptr2: " << *ptr2 << std::endl; return 0; // 离开作用域时,ptr1 和 ptr2 自动释放内存 } |
- 移动
unique_ptr
不允许复制,但支持移动- 使用
std::move
将所有权转移给另一个unique_ptr
1 2 3 4 5 6 7 |
std::unique_ptr<int> ptr1 = std::make_unique<int>(30); std::unique_ptr<int> ptr2 = std::move(ptr1); // 将 ptr1 的所有权转移给 ptr2 if (!ptr1) { std::cout << "ptr1 is now empty." << std::endl; } std::cout << "ptr2: " << *ptr2 << std::endl; |
- 使用自定义删除器
- 自定义删除器可以是函数指针、函数对象或 lambda 表达式,用于指定如何销毁对象
1 2 3 4 5 6 |
auto deleter = [](int* p) { std::cout << "Custom deleter called for " << *p << std::endl; delete p; }; std::unique_ptr<int, decltype(deleter)> ptr(new int(40), deleter); |
内部组成
- 原始指针
unique_ptr
内部保存了一个原始指针(T*
),指向由它管理的对象- 这个指针是
unique_ptr
的核心,它直接持有对象的地址,并通过它来访问和操作对象
- 自定义删除器
unique_ptr
支持自定义删除器,允许用户指定如何销毁和释放所管理的对象- 删除器可以是默认的
delete
操作,也可以是用户定义的函数、函数对象或 lambda 表达式
- 指针与删除器的组合
unique_ptr
通过一种名为“压缩对”(Compressed Pair
)的优化技术,将原始指针和删除器合并在一起,以减少内存占用- 如果删除器是空类(即没有成员变量的类),编译器可以优化它的存储,使得
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 |
#include <iostream> #include <memory> // 定义一个空类作为删除器 struct EmptyDeleter { void operator()(int* p) const { std::cout << "EmptyDeleter called for " << *p << std::endl; delete p; } }; int main() { // 使用空类删除器创建 unique_ptr std::unique_ptr<int, EmptyDeleter> ptr(new int(42)); std::cout << "Value: " << *ptr << std::endl; // 检查 unique_ptr 的大小 std::cout << "Size of unique_ptr: " << sizeof(ptr) << " bytes" << std::endl; return 0; } // Value: 42 // Size of unique_ptr: 8 bytes // EmptyDeleter called for 42 |
线程安全
unique_ptr
是独占对象所有权的智能指针,不允许复制,只允许移动- 这一设计确保了同一时间只有一个
unique_ptr
可以指向某个对象,因此不会有多个指针同时管理同一个对象的情况,从而避免了共享资源的复杂性
- 这一设计确保了同一时间只有一个
- 非线程安全的操作
- 操作
unique_ptr
本身并不线程安全
例如,两个线程同时对同一个unique_ptr
进行操作(如reset()
、release()
、或移动赋值)是非线程安全的,会导致数据竞争 - 对象的访问也不受保护
unique_ptr
不会保护其所指对象的内部数据的线程安全性,如果多个线程访问对象,需要额外的同步措施来保证安全
- 操作
- 线程安全的使用场景
- 在没有跨线程共享或修改
unique_ptr
本身的情况下,unique_ptr
是安全的
例如,将unique_ptr
移动到另一个线程中独占使用是安全的,因为没有多个线程同时操作同一个unique_ptr
实例
- 在没有跨线程共享或修改
安全的使用方式
- 将
unique_ptr
移动到其他线程unique_ptr
可以安全地通过std::move
转移到其他线程中,由新线程独占管理- 这样保证了对象的所有权在一个线程中,不存在并发访问的风险
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <memory> #include <thread> void threadFunc(std::unique_ptr<int> ptr) { std::cout << "Thread received: " << *ptr << std::endl; } int main() { std::unique_ptr<int> ptr = std::make_unique<int>(42); std::thread t(threadFunc, std::move(ptr)); // 将 unique_ptr 移动到另一个线程 t.join(); // 等待线程结束 // 此时 ptr 已被移动,不能再访问 if (!ptr) { std::cout << "ptr is now empty after move." << std::endl; } return 0; } |
- 避免多线程共享
unique_ptr
- 不要尝试在多个线程间共享
unique_ptr
实例,除非明确使用了同步机制(如互斥锁)来保护对unique_ptr
的访问
- 不要尝试在多个线程间共享
- 使用
std::mutex
保护unique_ptr
- 如果必须在多线程中访问同一个
unique_ptr
,需要使用std::mutex
或其他同步工具来保证线程安全
- 如果必须在多线程中访问同一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> #include <memory> #include <thread> #include <mutex> std::mutex mtx; std::unique_ptr<int> ptr = std::make_unique<int>(100); void threadFunc() { std::lock_guard<std::mutex> lock(mtx); if (ptr) { std::cout << "Thread read value: " << *ptr << std::endl; } } int main() { std::thread t1(threadFunc); std::thread t2(threadFunc); t1.join(); t2.join(); return 0; } |
工作原理
- 独占所有权
- 自动内存管理
- 自定义删除器
- 指针和删除器组合
与 shared_ptr
的区别
unique_ptr
:独占所有权,不可复制,只能移动shared_ptr
:可以共享所有权,有引用计数,多个shared_ptr
可以同时管理同一对象
unique_ptr
没有引用计数,操作简单且性能高shared_ptr
有引用计数,稍微复杂,开销较大,但在需要共享所有权时非常有用
release
release()
函数用于释放unique_ptr
对象的所有权,不调用删除器,也不会销毁对象,而是返回原始指针unique_ptr
变为空
1 2 3 |
std::unique_ptr<int> ptr(new int(40)); int* rawPtr = ptr.release(); // ptr 不再管理对象,rawPtr 指向 new int(40) delete rawPtr; // 需要手动销毁 |
reset
reset()
用于重置unique_ptr
,释放当前管理的对象(如果有),然后可选择性地用新对象替代管理
1 2 3 |
std::unique_ptr<int> ptr(new int(50)); ptr.reset(new int(60)); // 释放原来的 int(50),现在管理 int(60) ptr.reset(); // 释放 int(60),ptr 变为空 |
get
get()
返回unique_ptr
管理的原始指针,但不会转移所有权,主要用于访问对象
1 2 3 |
std::unique_ptr<int> ptr(new int(70)); int* rawPtr = ptr.get(); // 获取原始指针,但 ptr 仍管理对象 std::cout << "Value: " << *rawPtr << std::endl; |
swap
swap()
用于交换两个unique_ptr
管理的对象,两个指针交换了其管理的对象及删除器
1 2 3 |
std::unique_ptr<int> ptr1(new int(80)); std::unique_ptr<int> ptr2(new int(90)); ptr1.swap(ptr2); // ptr1 现在管理 90,ptr2 管理 80 |
operator*
和 operator->
unique_ptr
重载了*
和->
操作符,用于访问所管理的对象的成员
operator bool
- 提供了到
bool
类型的隐式转换,可以用于检查指针是否为空
std::move
- 由于
unique_ptr
不能复制(拷贝构造和赋值被删除),但可以通过std::move
转移所有权
1 2 |
std::unique_ptr<int> ptr1 = std::make_unique<int>(110); std::unique_ptr<int> ptr2 = std::move(ptr1); // ptr1 现在为空,ptr2 接管了所有权 |
注意事项
- 避免从
unique_ptr
中获取原始指针- 虽然可以使用
get()
获取unique_ptr
的原始指针,但这意味着绕过了智能指针的自动内存管理机制,使用时需要非常谨慎
- 虽然可以使用
- 不要使用原始指针构造多个
unique_ptr
- 从原始指针创建多个
unique_ptr
会导致同一对象被多次释放,导致未定义行为
- 从原始指针创建多个
- 合理使用
std::move
- 在需要转移所有权时,应使用
std::move
,但要确保不会误用,导致对象所有权丢失
- 在需要转移所有权时,应使用
场景
- 资源管理
unique_ptr
非常适合用于管理动态分配的内存、文件句柄、网络资源等,确保资源在不再需要时被正确释放
RAII
(资源获取即初始化)模式- 使用
unique_ptr
可以实现 RAII 模式,确保资源在对象生命周期结束时自动释放
- 使用
- 工厂函数的返回值
unique_ptr
常用作工厂函数的返回值,提供一种安全且明确的所有权转移方式
chromium智能指针
scoped_ptr
- 概述
- 一种独占所有权的智能指针,与
std::unique_ptr
类似 - 设计目的是在作用域结束时自动销毁所管理的对象,以确保资源不会泄漏
- 由于
scoped_ptr
不支持移动操作(如std::move
),在C++11
之后,它逐渐被std::unique_ptr
所取代,并在新的Chromium
代码中不再推荐使用
- 一种独占所有权的智能指针,与
- 特点
- 拥有独占的对象所有权,不支持复制和赋值
- 在
scoped_ptr
离开作用域时,自动调用对象的析构函数
scoped_refptr
-
概述
Chromium
中的引用计数智能指针,类似于std::shared_ptr
,但它具有更轻量级的实现,主要用于管理具有内部引用计数的对象- 适用于需要共享所有权的对象,特别是在多线程环境下常见的对象引用管理场景
-
特点
- 自动管理引用计数,在对象被所有指针释放后自动销毁
- 适用于具有
AddRef()
和Release()
方法的对象,常用于COM
对象或自定义引用计数对象 - 支持复制和赋值,引用计数会自动调整
-
工作原理
scoped_refptr
在构造时调用对象的AddRef()
方法增加引用计数,在析构时调用Release()
方法减少引用计数- 当
Release()
使引用计数归零时,自动调用对象的析构函数
-
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include "base/memory/scoped_refptr.h" class RefCountedResource : public base::RefCounted<RefCountedResource> { public: RefCountedResource() { std::cout << "Resource acquired\n"; } private: friend class base::RefCounted<RefCountedResource>; ~RefCountedResource() { std::cout << "Resource destroyed\n"; } }; int main() { scoped_refptr<RefCountedResource> ptr1 = new RefCountedResource(); // 引用计数为 1 { scoped_refptr<RefCountedResource> ptr2 = ptr1; // 引用计数增加到 2 std::cout << "Inside inner scope\n"; } // 离开内作用域,引用计数减少到 1 std::cout << "Outside inner scope\n"; return 0; // 离开主作用域,引用计数归零,自动销毁资源 } |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ C++17_第三篇06/29
- ♥ Pybind11记述:一07/04
- ♥ C++20_第一篇06/30
- ♥ C++标准模板库编程实战_序列容器12/06
- ♥ 深入理解C++11:C++11新特性解析与应用 一12/21
- ♥ 打包_7z生成自解压打包exe07/11