news 2026/6/11 1:42:55

C++ 智能指针完全指南(二):shared_ptr 深度详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ 智能指针完全指南(二):shared_ptr 深度详解

引言

上一篇我们学习了unique_ptr——独占所有权的智能指针。它解决了"忘记 delete"的问题,但它的独占特性意味着一个对象只能有一个 owner

现实中有大量场景需要共享所有权:多个窗口共享同一个数据模型、多个线程共享同一个资源、缓存中的对象被多处引用。这时就需要shared_ptr

shared_ptr通过引用计数来管理共享所有权:每多一个shared_ptr指向同一个对象,计数 +1;每销毁一个,计数 -1。计数归零时,对象自动释放。

第一部分:shared_ptr 的核心原理

一、引用计数

#include <memory> #include <iostream> using namespace std; int main() { shared_ptr<int> p1 = make_shared<int>(42); cout << "引用计数:" << p1.use_count() << endl; // 1 { shared_ptr<int> p2 = p1; // p2 和 p1 共享同一个对象 cout << "引用计数:" << p1.use_count() << endl; // 2 cout << "引用计数:" << p2.use_count() << endl; // 2(相同) shared_ptr<int> p3 = p1; cout << "引用计数:" << p1.use_count() << endl; // 3 } // p3 和 p2 离开作用域,引用计数 -2 cout << "引用计数:" << p1.use_count() << endl; // 1 } // p1 离开作用域,引用计数归零 → 自动 delete

二、控制块

shared_ptr不只是把裸指针包起来,它背后有一个控制块

三、创建方式

// 方式1:make_shared(最推荐!) auto p1 = make_shared<int>(42); // 方式2:用 new(不推荐) shared_ptr<int> p2(new int(42)); // 方式3:从 unique_ptr 移动 unique_ptr<int> up = make_unique<int>(42); shared_ptr<int> p3 = std::move(up); // up 变成 nullptr

为什么优先用make_shared

创建方式内存分配次数内存布局
make_shared1 次对象和控制块在同一块内存中
new + shared_ptr2 次对象一块、控制块一块

第二部分:shared_ptr 的常用操作

一、基本操作

auto p1 = make_shared<int>(42); // 访问 cout << *p1 << endl; // 解引用 *p1 = 100; // 修改 // 获取原始指针 int* raw = p1.get(); // 不增加引用计数,不转移所有权 // 判断是否为空 if (p1) { /* p1 不为空 */ } if (p1 != nullptr) { /* 等价 */ } // 引用计数 cout << p1.use_count() << endl; // 是否是唯一持有者 if (p1.unique()) { /* 引用计数 == 1 */ } // 释放 p1.reset(); // 引用计数 -1,归零则释放对象 p1.reset(new int(200)); // 释放旧对象,接管新对象

二、作为函数参数

// ✅ 值传递:增加引用计数 void func(shared_ptr<int> p) { cout << p.use_count() << endl; // 2(调用方的 1 + 这里的 1) } // ✅ const 引用:不增加引用计数,只读 void func2(const shared_ptr<int>& p) { cout << *p << endl; } // ✅ 如果只需要对象本身,传引用就行 void func3(int& value) { value++; } int main() { auto p = make_shared<int>(42); func(p); // 引用计数 +1 然后 -1 func2(p); // 引用计数不变 func3(*p); // 直接操作对象,不涉及 shared_ptr }

第三部分:shared_ptr 的线程安全性

#include <mutex> #include <thread> auto p = make_shared<int>(0); mutex mtx; // ✅ shared_ptr 本身可以被多个线程安全拷贝 thread t1([p] { // 引用计数 +1 lock_guard<mutex> lock(mtx); (*p)++; // 保护对象操作 }); thread t2([p] { // 引用计数 +1 lock_guard<mutex> lock(mtx); (*p)++; }); // ✅ 引用计数正确 = 3(主线程1 + t1 + t2)

第四部分:自定义删除器

// 管理 FILE* auto fileDeleter = [](FILE* f) { if (f) fclose(f); }; shared_ptr<FILE> filePtr(fopen("test.txt", "w"), fileDeleter); fprintf(filePtr.get(), "hello\n"); // 离开作用域 → fclose 自动调用

和 unique_ptr 的区别shared_ptr的自定义删除器存储在控制块中,不同类型删除器的shared_ptr可以互相赋值

shared_ptr<FILE> p1(fopen("a.txt", "r"), fclose); shared_ptr<FILE> p2 = p1; // ✅ 删除器会被正确共享

第五部分:enable_shared_from_this

一、问题场景

class Widget { public: shared_ptr<Widget> getSharedPtr() { // ❌ 错误!每次都创建新的控制块,引用计数独立! return shared_ptr<Widget>(this); } }; auto p1 = make_shared<Widget>(); auto p2 = p1->getSharedPtr(); // p1 和 p2 各有独立的引用计数! // p1 释放 → 对象被 delete // p2 释放 → 再次 delete → 崩溃!

二、解决方案

class Widget : public enable_shared_from_this<Widget> { public: shared_ptr<Widget> getSharedPtr() { return shared_from_this(); // ✅ 正确,复用已有的控制块 } }; auto p1 = make_shared<Widget>(); auto p2 = p1->getSharedPtr(); // p1 和 p2 共享同一个控制块 cout << p1.use_count() << endl; // 2

必须已经有shared_ptr管理该对象,否则shared_from_this()会抛异常。


第六部分:常见错误

// ❌ 错误1:把同一个裸指针给多个 shared_ptr int* raw = new int(42); shared_ptr<int> p1(raw); // 引用计数 = 1 shared_ptr<int> p2(raw); // 新的引用计数 = 1,不是 2! // p1 释放 → delete raw // p2 释放 → 再次 delete raw → 崩溃! // ✅ 正确:用拷贝 auto p1 = make_shared<int>(42); auto p2 = p1; // 引用计数 = 2 // ❌ 错误2:在不需要的时候传值 void func(shared_ptr<int> p) { ... } // 每次调用引用计数 +1/-1 // ✅ 传引用(不改变所有权时) void func(const shared_ptr<int>& p) { ... } // ✅ 传裸引用(只需要对象本身时) void func(int& v) { ... }

总结

一、核心要点

特性说明
所有权共享,多个 shared_ptr 指向同一对象
创建make_shared<T>(args)
引用计数use_count(),计数归零自动释放
线程安全引用计数安全,对象操作需要额外加锁
自定义删除器存在控制块中,可以共享
enable_shared_from_thisthis安全创建 shared_ptr
头文件<memory>

二、unique_ptr vs shared_ptr

对比unique_ptrshared_ptr
所有权独占共享
大小8 字节(无删除器)16 字节
性能原始指针级别引用计数有开销
创建make_uniquemake_shared
拷贝
使用场景明确单一 owner多个 owner

三、一句话记忆

shared_ptr用引用计数实现共享所有权,计数归零自动释放。make_shared一次分配省内存,引用计数本身线程安全但对象操作需加锁。从this创建shared_ptr需继承enable_shared_from_this

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/11 1:41:59

想在广东找到专业靠谱的退税机构,这些筛选方法值得你参考

引言 近年来&#xff0c;广东外贸出口、先进制造产业持续增长&#xff0c;越来越多企业开始重视企业退税政策的落地&#xff0c;希望借助专业机构的能力&#xff0c;合规享受退税红利&#xff0c;缓解资金周转压力。但市面上财税服务机构良莠不齐&#xff0c;不少企业踩过退税…

作者头像 李华
网站建设 2026/6/11 1:36:53

C++写的轻量QR码编码器,纯头文件+源码,不依赖第三方库

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;提供一套完整可直接编译运行的C QR码编码实现&#xff0c;核心逻辑集中在QR_Encode.h和QR_Encode.cpp两个文件中&#xff0c;支持标准QR码版本1-40、四种纠错等级&#xff08;L/M/Q/H&#xff09;、数字/字母/字…

作者头像 李华
网站建设 2026/6/11 1:33:54

开源音源终极配置指南:三步解锁全网无损音乐库

开源音源终极配置指南&#xff1a;三步解锁全网无损音乐库 【免费下载链接】lxmusic- lxmusic(洛雪音乐)全网最新最全音源 项目地址: https://gitcode.com/gh_mirrors/lx/lxmusic- 你是否厌倦了在各个音乐平台间来回切换&#xff1f;是否被高昂的会员费和分散的版权困扰…

作者头像 李华
网站建设 2026/6/11 1:32:54

MC9S12XE Flash操作实战:从寄存器配置到安全编程避坑指南

1. 项目概述与Flash操作的核心挑战在嵌入式开发&#xff0c;尤其是汽车电子和工业控制领域&#xff0c;MC9S12XE系列微控制器因其高可靠性和实时性被广泛应用。这类应用对固件的在线升级&#xff08;OTA&#xff09;、参数存储和故障安全机制有着严苛的要求&#xff0c;而这一切…

作者头像 李华