目录
- unique_ptr
- 所有权转移
- 所有权释放
- make_unique 和直接(new)unique_ptr
- 区别一 :需要一次性处理多个资源分配的地方make_unique比unique_ptr更安全
- 区别二 :直接new支持自定义删除器
- shared_ptr
- shared_ptr智能指针指向同一个对象的不同成员
- 循环引用问题
- 为什么weak_ptr能解决shared_ptr的循环引用问题
- make_shared 和 直接 new 一个shared_ptr的区别
- enable_shared_from_this 和 shared_from_this()
- shared_ptr的线程安全问题
unique_ptr
所有权转移
unique_ptr不能被拷贝和用于赋值,因为unique_ptr删掉了这两个函数
但是底层源码重载了传右值的拷贝构造
所以可以通过std::move来通过转移所有权unique_编程客栈ptr<Data> p6(new Data()); //不可复制构造和赋值复制 //unique_ptr<Data>p7 = p6; 错误 //p6释放所有权 转移到p7 unique_ptr<Data>p7 = move(p6); unique_ptr<Data>p8(new Data()); p7 = move(p8);//重新移动赋值,原有的p6会被释放掉 //重置空间,原空间清理 p7.reset(new Data());
所有权释放
注意!当unique_ptr释放所有权以后智能指针就不会再管理这块空间,需要自己手动释放空间!
//释放所有权 unique_ptr<Data>p9(new Data()); auto ptr9 = p9.release();//注意,release释放所有权以后要自己清理空间 delete ptr9;//!!!!!!
make_unique 和直接(new)unique_ptr
区别一 :需要一次性处理多个资源分配的地方make_unique比unique_ptr更安全
void process_data( std::unique_ptr<Data> p1, std::unique_ptr<Data> p2 ); process_data( std::unique_ptr<Data>(new Data("A")), // 分配资源 A std::unique_ptr<Data>(new Data("B")) // 分配资源 B );
编译器在构造函数参数时,执行顺序是不确定的。可能的执行顺序例如:
- new Data(“A”) → 成功,得到一个裸指针 A
- new Data(“B”) → 成功,得到一个裸指针 B*
- 构造unique_ptr 接管 A*
- 构造 unique_ptr 接管 B*
如果中间发生异常:
- new Data(“A”) → 成功,得到 A*
- new Data(“B”) → 抛出异常(例如内存不足) 此时 A* 尚未被 unique_ptr 接管! 异常被抛出后,裸指针 A* 无法被自动释放 → 内存泄漏。
如果选用make_unique
process_data( std::make_unique<Data>("A"), // 直接构造并接管资源 A std::make_unique<Data>("B") // 直接构造并接管资源 B );
此时每一步的执行:
- make_unique(“A”) → 立即构造对象并封装到 unique_ptr,无裸指针暴露
- make_unique(“B”) → 同上 如果 make_unique(“B”) 抛出异常:make_unique(“A”) 已经返回的 unique_ptr 会正常析构 → 资源 A 被自动释放没有泄漏!
区别二 :直接new支持自定义删除器
new支持在构造 unique_ptr 时指定自定义删除器
std::unique_ptr<MyClass, Deleter> p(new MyClass, custom_deleter);
但是make_unique不支持,只能使用默认的 delete 操作符。
shared_ptr
shared_ptr智能指针指向同一个对象的不同成员
sc2和sc3 分别 指向sc1的index1成员和index2成员,使sc1的引用计数+2
class Data { public: Data() { cout&pythonlt;< "Begin Data" << endl; } ~Data() { cout<< "End Data" &landroidt;< endl; } int index1 = 0; int index2 = 0; }; { shared_ptr<Data>sc1(new Data); //打印引用计数 = 1 cout << "sc1.use_count() = " << sc1.use_count() << endl; shared_ptr<int>sc2(sc1,&sc1->index1);//引用计数+1 shared_ptr<int>sc3(sc1, &sc1->index2);//引用计数+1 //打印引用计数 = 3 cout << "sc1.use_count() = " << sc1.use_count() << endl; }
循环引用问题
当两个或多个对象通过 shared_ptr 互相持有对方时,它们的引用计数永远不会归零,导致内存无法释放。
class A { public: A() { cout << "Create A" << endl; } ~A() { cout << " Drop A " << endl; } void Do() { cout << "Do b2.use_count() = " << b2.use_count() << endl; auto b = b2.lock(); //复制一个shared_ptr 引用计数加一 cout << "Do b2.use_count() = " << b2.use_count() << endl; } shared_ptr<B> b1;//强智能指针 weak_ptr<B> b2; //弱智能指针 }; class B { public: B() { cout << "Create B" << endl; } ~B(){ cout << " Drop B " << endl; } shared_ptr<A> a1;//强智能指针 weak_ptr<A> a2; //弱智能指针 }; { auto a = make_shared<A>();//a引用计数+1 auto b = make_shared<B>();//b引用计数+1 a->b1 = b;//b引用计数+1 //出作用域前,引用计数为2 cout << "a->b1 = b;b.use_count()=" << b.use_count() << endl; b->a1 = a;//a引用计数+1 //出作用域前,引用计数为2 cout << "b->a1 = a;a.use_count()=" << a.use_count() << endl; }
由于调用问题,导致出作用域以后a和b的引用计数都还是1,所以空间没有被释放
改成使用weak_ptr
{ auto a = make_shared<A>(); auto b = make_shared<B>(); a->b2 = b;//weak_ptr 引用计数不加一 a->Do();//Do函数里引用计数加一,出Do函数作用域减一 cout << "a->b2 = b;b.use_count()=" << b.use_count() << endl; b->a2 = a;//引用计数不加一 cout << "b->a2 = a;a.use_count()=" << a.use_count() << endl; }//不会产生循环引用问题
为什么weak_ptr能解决shared_ptr的循环引用问题
weak_ptr 本身不拥有资源所有权:
它只是观察 shared_ptr 管理的对象,不会增加引用计数。所以a->b2 = b;这句不会增加b的引用计数
同理b->a2 = a;这句也不会增加a的引用计数但是由于weak_ptr只是一个观察者,无法访问任何资源,仅“观察”资源,不拥有所有权如果想要访问资源该怎么办呢?
如同a->Do();里做的那样
只要原 shared_ptr(即 a)未释放资源,就可以通过 lock() 获取有效的 shared_ptr 并访问。void Do() { cout << "Do b2.use_count() = " << b2.use_count() << endl; auto b = b2.lock(); //返回一个shared_ptr 引用计数加一 //这里还可以做其他的访问shared_ptr资源的操作 cout << "Do b2.use_count() = " << b2.use_count() <rXIKP;< endl; }//出作用域加上的那个引用计数自动-1
通过weak_ptr.lock()来 返回 一个 shared_ptr 并将引用计数加一
(注意!只有当原shared_ptr对象还存在的时候才会返回shared_ptr,否者返回nullptr)
除了放函数里,还能放判断条件里
if (auto a_shared = b->a_weak.lock()) { // 返回nullptr 说明shared_ptr被释放 //不会执行此处 } else { //引用计数+1 std::cout << "A is already destroyed!" << std::endl; // 输出此句 }//引用计数-1
通过这种方式,weak_ptr 可以安全地观察资源,而不会导致循环引用或内存泄漏
make_shared 和 直接 new 一个shared_ptr的区别
区别一:make_shared资源分配更安全
原因和make_unique一样,这里就不再赘述了
区别二:直接new支持自定义删除器
区别三:内存分配方式不同
由于shared_ptr除了维护对象本身的内存以外还要维护一个控制块
- 强引用计数(use_count):记录有多少个 shared_ptr 共享对象
- 弱引用计数(weak_count):记录有多少个weak_ptr 观察对象
- 自定义删除器(如果存在)。
- 对象指针(指向实际分配的对象)。
make_shared 在底层会 一次性分配一块连续内存,既存储对象本身,也存储控制块。
但是new shared_ptr会有两次分配
std::shared_ptr<MyClass> p(new MyClass);
- 第一次编程客栈分配:new MyClass 分配对象内存。
- 第二次分配:shared_ptr 构造函数内部为控制块分配内存。
为什么make_unique和直接new unique_ptr都是一次分配内存
因为unique_ptr 不需要维护引用计数,因此 没有控制块。无论通过 make_unique 还是直接 new,都只需分配对象内存
shared_ptr合并分配的缺点
对象和控制块内存绑定,即使所有 shared_ptr 销毁,若仍有 weak_ptr 存在,对象内存仍然需等待控制块释放(但析构函数会被及时调用)
shared_ptr控制块内存的延迟释放是内存泄漏吗?
由于make_shared 是一次性分配一块连续内存,同时存储 对象实例 和 控制块(包含引用计数、弱引用计数等)
所以当没有weak_ptr存在时
通过make_shared建立的shared_ptr:
- 对象析构函数立即被调用。
- 整块内存(对象 + 控制块)立即释放。
通过new建立的shared_ptr:
- 对象内存立即释放。
- 控制块内存也立即释放。
当有weak_ptr存在时
通过make_shared建立的shared_ptr:
- 对象析构函数被调用(资源清理)。
- 对象内存和控制块内存暂时保留(直到所有 weak_ptr 也被销毁)。
通过new建立的shared_ptr:
- 对象内存立即释放(仅保留控制块内存)。
由于make_shared对象和控制块内存是连续的,无法单独释放对象内存。所以必须等待所有 weak_ptr 销毁后,整块内存才能一起释放。
那这是内存泄漏吗?
不是! 内存泄漏的定义是:无法再访问且未释放的内存。
而在此时:对象析构函数已被调用(资源已清理)。内存仍被 weak_ptr 的控制块管理,虽然未释放,但程序仍能通过 weak_ptr 的机制感知到内存状态。当所有 weak_ptr 销毁后,内存会被正确释放。enable_shared_from_this 和 shared_from_this()
解决场景:
当一个对象已被 shared_ptr 管理时,若在成员函数中直接用 this 创建新的 shared_ptr,会导致 多个独立的引用计数 。当某一 shared_ptr 析构时,对象可能被提前销毁,引发悬空指针或双重释放。
问题场景示例1
class Backgroundworker { public: void startWork() { // 错误:裸指针 this 跨线程传递,主线程可能提前释放对象 std::thread([this]() { doWork(); // 可能访问已销毁对象! }).detach(); } void doWork() { /* 自己实现的操作 */ } }; int main() { auto worker = std::make_shared<BackgroundWorker>(); worker->startWork(); worker.reset(); // 主线程释放对象 // 子线程仍在执行 doWork(),this 已失效! }
enable_shared_from_this 内部保存一个 weak_ptr,指向与对象关联的 shared_ptr 控制块。通过 shared_from_this() 方法生成与已有 shared_ptr 共享所有权的 shared_ptr 确保引用计数一致。
解决示例
通过获取与 shared_ptr 共享所有权的智能指针,延长对象生命周期class BackgroundWorker : public std::enable_shared_from_this<BackgroundWorker> { public: void startWork() { // 正确:通过 shared_from_this() 传递所有权 std::thread([self = shared_from_this()]() { self->doWork(); // 安全:self 确保对象存活 }).detach(); } // ... };
问题场景示例2:
class TreeNode { public: void addChild() { // 创建一个子节点 child,用 shared_ptr 管理 auto child = std::make_shared<TreeNode>(); // 错误:直接用 this 创建新的 shared_ptr,赋值给 child->parent //child->parent引用计数也是 1 child->parent = std::shared_ptr<TreeNode>(this); // 危险! // 将 child 添加到当前节点的 children 列表中 children.push_back(child); } std::shared_ptr<TreeNode> parent; // 父节点用 shared_ptr 管理 std::vector<std::shared_ptr<TreeNode>> children; // 子节点列表 }; int main() { // 创建一个根节点 root,用 shared_ptr 管理 auto root = std::make_shared<TreeNode>();//root引用计数是 1 root->addChild(); // 调用 addChild,导致双重释放! }
这段代码的关键问题出现在 child->parent = std::shared_ptr(this)
this 是当前 TreeNode 对象的裸指针(即 root 指向的对象)。直接用 this 创建了一个新的 shared_ptr,赋值给 child->parent。这个新的 shared_ptr 与 root 是 完全独立 的,它不知道 root 的存在,因此它的引用计数 “也是 1”。当 main 函数结束时,root 的引用计数减为 0,会释放它指向的 TreeNode 对象。
但此时 child->parent 也指向同一个 TreeNode 对象,且它的引用计数仍然是 1(如果 child 未被销毁)。当 child 析构时,child->parent 的引用计数减为 0,会再次尝试释放同一个 TreeNode 对象,导致** 双重释放**。
解决示例:
class TreeNode : public std::enable_shared_from_this<TreeNode> { public: void addChild() { auto child = std::make_shared<TreeNode>(); // 通过 shared_from_this() 获取与 root 共享所有权的 shared_ptr child->parent = shared_from_this(); // 引用计数 +1 children.push_back(child); } std::shared_ptr<TreeNode> parent; std::vector<std::shared_ptr<TreeNode>> children; }; int main() { auto root = std::make_shared<TreeNode>(); root->addChild(); // 安全:root 的引用计数变为 2 }
shared_ptr的线程安全问题
https://www.jb51.net/article/41353.htm
到此这篇关于C++ unique_ptr、shared_ptr、weak_ptr的区别小结的文章就介绍到这了,更多相关C++ unique_ptr shared_ptr weak_ptr内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论