当前位置: 首页 > news >正文

欢迎来到std::shared_ptr的派对!

<摘要>
欢迎来到std::shared_ptr的奇妙世界!这可不是什么无聊的内存管理教程,而是一场关于C++智能指针的探险之旅。想象一下,你有个珍贵的玩具,一群小朋友都想玩——std::shared_ptr就是那个确保玩具被安全共享、不会丢失或损坏的聪明管理员!我们将从它的"前世今生"开始,揭秘它如何从C++的内存管理困境中诞生;然后深入它的"大脑结构"(控制块和引用计数);接着通过实际案例看它如何大显身手;最后还会教你如何正确"驾驭"这头强大的野兽。无论你是C++新手还是老司机,这篇指南都会让你笑着学会shared_ptr的精髓!


<解析>

🎉 欢迎来到std::shared_ptr的派对!

嘿!你知道吗?在C++的世界里,内存管理曾经是个让人头疼的"派对破坏者"——总是有人忘记清理垃圾(内存泄漏),或者提前赶走客人(悬空指针)。然后std::shared_ptr闪亮登场,它就像个超级派对组织者,确保每个人都能玩得开心,而且派对结束后场地自动清理得干干净净!

1. 🎯 背景与核心概念:shared_ptr的"前世今生"

1.1 "黑暗时代"的记忆

还记得C++的"远古时期"吗?那时候程序员们都是"内存管理忍者",手动分配和释放内存:

void risky_business() {int* ptr = new int(42);  // 申请内存if (rand() % 2 == 0) {throw "Oops!";  // 异常!内存泄漏了!delete ptr;     // 这行永远执行不到}delete ptr;  // 必须记得清理
}

这种代码就像在雷区跳舞——一步走错就爆炸!于是,智能指针概念应运而生。

1.2 shared_ptr的超级技能:引用计数

想象一下,你有一本超受欢迎的书:

  • 第一个借书的人:在便利贴上写"1"
  • 第二个人借:改成"2"
  • 还有人借:数字继续增加
  • 有人还书:数字减少
  • 当数字变成0:图书管理员把书收回书架

这就是shared_ptr的魔法!🎩✨

shared_ptr 实例1
控制块
shared_ptr 实例2
shared_ptr 实例3
引用计数: 3
托管对象

1.3 核心概念速成班

术语通俗解释技术定义
引用计数有多少人在使用这个资源跟踪当前指向对象的shared_ptr数量
控制块资源的"身份证"存储元数据的内存块
所有权共享大家共同保管钥匙多个shared_ptr实例管理同一对象
自动销毁最后一个离开的人关灯当引用计数为0时自动释放资源

2. 💡 设计意图与考量:为什么shared_ptr这么设计?

2.1 设计目标:做个"贴心小秘书"

shared_ptr被设计成这样的原因:

  1. 不要让我思考:自动化内存管理,解放程序员大脑
  2. 安全第一:避免内存泄漏和悬空指针
  3. 人人共享:支持多个所有者共享资源
  4. 灵活应变:支持自定义删除器和分配器

2.2 精妙的权衡艺术

设计shared_ptr就像设计瑞士军刀——要在各种功能间找到平衡:

35%25%20%20%shared_ptr设计权衡安全性性能灵活性易用性

具体权衡细节

设计选择带来的好处付出的代价
原子引用计数线程安全性能开销
控制块分离支持weak_ptr额外内存分配
类型擦除支持自定义删除器运行时开销

2.3 UML内部结构大揭秘

拥有
引用
shared_ptr
- control_block* ctrl_block
- T* ptr
+use_count()
+reset()
+get()
+operator*()
+operator->()
control_block
- atomic ref_count
- atomic weak_count
- void* managed_object
-void(*deleter)
+increment()
+decrement()
weak_ptr
- control_block* ctrl_block
- T* ptr
+lock()
+expired()

3. 🚀 实例与应用场景:shared_ptr实战秀

3.1 案例一:资源共享池(像共享单车一样方便)

想象有个数据库连接池,多个服务可以共享使用:

#include <iostream>
#include <memory>
#include <unordered_map>
#include <string>// 数据库连接类
class DatabaseConnection {
public:DatabaseConnection(const std::string& connStr) : connectionString(connStr) {std::cout << "🔗 创建连接: " << connStr << std::endl;}~DatabaseConnection() {std::cout << "🗑️  关闭连接: " << connectionString << std::endl;}void query(const std::string& sql) {std::cout << "📊 执行查询: " << sql << std::endl;}private:std::string connectionString;
};// 连接池管理
class ConnectionPool {
public:std::shared_ptr<DatabaseConnection> getConnection(const std::string& key) {auto it = pool.find(key);if (it != pool.end()) {std::cout << "🎯 复用现有连接" << std::endl;return it->second;}std::cout << "🆕 创建新连接" << std::endl;auto conn = std::make_shared<DatabaseConnection>(key);pool[key] = conn;return conn;}void cleanup() {for (auto it = pool.begin(); it != pool.end(); ) {if (it->second.use_count() == 1) {std::cout << "🧹 清理未使用连接: " << it->first << std::endl;it = pool.erase(it);} else {++it;}}}private:std::unordered_map<std::string, std::shared_ptr<DatabaseConnection>> pool;
};// 使用示例
int main() {ConnectionPool pool;// 服务A获取连接auto connA = pool.getConnection("mysql://user@localhost/db");connA->query("SELECT * FROM users");// 服务B共享同一连接auto connB = pool.getConnection("mysql://user@localhost/db");connB->query("SELECT * FROM orders");std::cout << "当前引用计数: " << connA.use_count() << std::endl;// 释放连接connA.reset();connB.reset();pool.cleanup();return 0;
}

运行结果

🆕 创建新连接
🔗 创建连接: mysql://user@localhost/db
📊 执行查询: SELECT * FROM users
🎯 复用现有连接
📊 执行查询: SELECT * FROM orders
当前引用计数: 3
🧹 清理未使用连接: mysql://user@localhost/db
🗑️  关闭连接: mysql://user@localhost/db

3.2 案例二:观察者模式(像杂志订阅一样优雅)

出版者不需要关心订阅者的生命周期:

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>// 前向声明
class NewsPublisher;// 观察者接口
class Subscriber {
public:virtual ~Subscriber() = default;virtual void update(const std::string& news) = 0;
};// 发布者类
class NewsPublisher : public std::enable_shared_from_this<NewsPublisher> {
public:void subscribe(const std::shared_ptr<Subscriber>& sub) {subscribers.push_back(sub);std::cout << "📩 新订阅者加入,当前订阅数: " << subscribers.size() << std::endl;}void unsubscribe(const std::shared_ptr<Subscriber>& sub) {subscribers.erase(std::remove(subscribers.begin(), subscribers.end(), sub),subscribers.end());std::cout << "👋 订阅者取消,剩余订阅数: " << subscribers.size() << std::endl;}void publishNews(const std::string& news) {std::cout << "📢 发布新闻: " << news << std::endl;for (const auto& sub : subscribers) {sub->update(news);}}private:std::vector<std::shared_ptr<Subscriber>> subscribers;
};// 具体订阅者
class EmailSubscriber : public Subscriber {
public:EmailSubscriber(const std::string& email) : email(email) {}void update(const std::string& news) override {std::cout << "📧 发送邮件到 " << email << ": " << news << std::endl;}private:std::string email;
};int main() {auto publisher = std::make_shared<NewsPublisher>();auto subscriber1 = std::make_shared<EmailSubscriber>("alice@example.com");auto subscriber2 = std::make_shared<EmailSubscriber>("bob@example.com");publisher->subscribe(subscriber1);publisher->subscribe(subscriber2);publisher->publishNews("C++23新特性发布!");publisher->unsubscribe(subscriber1);publisher->publishNews("shared_ptr性能优化技巧");return 0;
}

3.3 案例三:多态对象管理(像变形金刚一样灵活)

#include <iostream>
#include <memory>
#include <vector>// 图形基类
class Shape {
public:virtual ~Shape() { std::cout << "形状被销毁" << std::endl; }virtual void draw() const = 0;virtual double area() const = 0;
};// 圆形
class Circle : public Shape {
public:Circle(double r) : radius(r) {}void draw() const override { std::cout << "🔵 绘制圆形,半径: " << radius << std::endl; }double area() const override { return 3.14159 * radius * radius; }
private:double radius;
};// 矩形
class Rectangle : public Shape {
public:Rectangle(double w, double h) : width(w), height(h) {}void draw() const override { std::cout << "🟦 绘制矩形,尺寸: " << width << "x" << height << std::endl; }double area() const override { return width * height; }
private:double width, height;
};// 图形管理器
class ShapeManager {
public:void addShape(std::shared_ptr<Shape> shape) {shapes.push_back(shape);}void drawAll() const {std::cout << "\n🎨 绘制所有形状:" << std::endl;for (const auto& shape : shapes) {shape->draw();std::cout << "   面积: " << shape->area() << std::endl;}}double totalArea() const {double total = 0;for (const auto& shape : shapes) {total += shape->area();}return total;}private:std::vector<std::shared_ptr<Shape>> shapes;
};int main() {ShapeManager manager;manager.addShape(std::make_shared<Circle>(5.0));manager.addShape(std::make_shared<Rectangle>(3.0, 4.0));manager.addShape(std::make_shared<Circle>(2.5));manager.drawAll();std::cout << "📐 总面积: " << manager.totalArea() << std::endl;return 0;
}

4. 🔧 代码实现与流程图:自己动手造轮子

4.1 简化版shared_ptr实现

让我们亲手打造一个"迷你版"shared_ptr,理解其内部机制:

#include <iostream>
#include <atomic>// 控制块类
template<typename T>
class ControlBlock {
public:std::atomic<size_t> ref_count;T* managed_object;// 构造函数:创建新对象template<typename... Args>ControlBlock(Args&&... args) : ref_count(1), managed_object(new T(std::forward<Args>(args)...)) {std::cout << "🎯 创建控制块,引用计数: 1" << std::endl;}// 析构函数~ControlBlock() {delete managed_object;std::cout << "🧹 销毁控制块和对象" << std::endl;}// 增加引用计数void add_ref() {ref_count.fetch_add(1, std::memory_order_relaxed);std::cout << "➕ 引用计数增加: " << ref_count.load() << std::endl;}// 减少引用计数,返回是否需要销毁bool release() {if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {std::cout << "➖ 最后引用,准备销毁" << std::endl;return true;}std::cout << "➖ 引用计数减少: " << ref_count.load() << std::endl;return false;}
};// 简化版shared_ptr
template<typename T>
class SimpleSharedPtr {
public:// 默认构造函数SimpleSharedPtr() : ctrl_block(nullptr) {}// 构造函数:创建新对象template<typename... Args>explicit SimpleSharedPtr(Args&&... args) {ctrl_block = new ControlBlock<T>(std::forward<Args>(args)...);}// 拷贝构造函数SimpleSharedPtr(const SimpleSharedPtr& other) : ctrl_block(other.ctrl_block) {if (ctrl_block) {ctrl_block->add_ref();}}// 移动构造函数SimpleSharedPtr(SimpleSharedPtr&& other) noexcept : ctrl_block(other.ctrl_block) {other.ctrl_block = nullptr;std::cout << "🚀 移动构造,所有权转移" << std::endl;}// 析构函数~SimpleSharedPtr() {if (ctrl_block && ctrl_block->release()) {delete ctrl_block;}}// 拷贝赋值运算符SimpleSharedPtr& operator=(const SimpleSharedPtr& other) {if (this != &other) {// 释放当前资源if (ctrl_block && ctrl_block->release()) {delete ctrl_block;}// 复制新资源ctrl_block = other.ctrl_block;if (ctrl_block) {ctrl_block->add_ref();}}return *this;}// 移动赋值运算符SimpleSharedPtr& operator=(SimpleSharedPtr&& other) noexcept {if (this != &other) {// 释放当前资源if (ctrl_block && ctrl_block->release()) {delete ctrl_block;}// 接管资源ctrl_block = other.ctrl_block;other.ctrl_block = nullptr;std::cout << "🚀 移动赋值,所有权转移" << std::endl;}return *this;}// 访问运算符T& operator*() const { return *ctrl_block->managed_object; }T* operator->() const { return ctrl_block->managed_object; }// 获取引用计数size_t use_count() const { return ctrl_block ? ctrl_block->ref_count.load() : 0; }// 检查是否为空explicit operator bool() const { return ctrl_block != nullptr; }private:ControlBlock<T>* ctrl_block;
};// 测试代码
class TestObject {
public:TestObject(int value) : data(value) {std::cout << "📦 创建TestObject: " << data << std::endl;}~TestObject() {std::cout << "🗑️  销毁TestObject: " << data << std::endl;}void print() const {std::cout << "📋 TestObject值: " << data << std::endl;}private:int data;
};int main() {std::cout << "=== 开始测试SimpleSharedPtr ===" << std::endl;// 创建第一个shared_ptrSimpleSharedPtr<TestObject> ptr1(42);ptr1->print();std::cout << "引用计数: " << ptr1.use_count() << std::endl;{// 拷贝构造SimpleSharedPtr<TestObject> ptr2 = ptr1;ptr2->print();std::cout << "引用计数: " << ptr1.use_count() << std::endl;// 移动构造SimpleSharedPtr<TestObject> ptr3 = std::move(ptr2);if (!ptr2) {std::cout << "ptr2已变为空" << std::endl;}std::cout << "引用计数: " << ptr1.use_count() << std::endl;} // ptr3离开作用域std::cout << "引用计数: " << ptr1.use_count() << std::endl;std::cout << "=== 测试结束 ===" << std::endl;return 0;
}

4.2 shared_ptr操作流程图

创建shared_ptr
分配控制块和对象
设置引用计数=1
拷贝构造
引用计数原子增加
新指针指向同一控制块
移动构造
转移控制块所有权
原指针置为空
析构shared_ptr
引用计数原子减少
引用计数==0?
删除托管对象
删除控制块
结束
reset操作
减少当前引用计数
指向新对象或nullptr

4.3 Makefile范例

# 🛠️  Smart Pointer演示项目的Makefile# 编译器设置
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -O2 -g# 目标文件
TARGETS := shared_ptr_demo simple_shared_ptr_demo
ALL_OBJS := shared_ptr_demo.o simple_shared_ptr_demo.o# 默认目标
all: $(TARGETS)# 主演示程序
shared_ptr_demo: shared_ptr_demo.o$(CXX) $(CXXFLAGS) -o $@ $^# 简化版shared_ptr演示
simple_shared_ptr_demo: simple_shared_ptr_demo.o$(CXX) $(CXXFLAGS) -o $@ $^# 编译源文件
%.o: %.cpp$(CXX) $(CXXFLAGS) -c $< -o $@# 清理
clean:rm -f $(TARGETS) $(ALL_OBJS)# 运行所有演示
run: all@echo "运行标准shared_ptr演示..."@./shared_ptr_demo@echo ""@echo "运行简化版shared_ptr演示..."@./simple_shared_ptr_demo# 内存检查
memcheck: shared_ptr_demovalgrind --leak-check=full ./shared_ptr_demo.PHONY: all clean run memcheck

编译和运行方法

# 编译所有目标
make# 运行所有演示
make run# 清理生成的文件
make clean# 内存泄漏检查
make memcheck

5. 🔄 交互性内容解析:多线程下的shared_ptr舞蹈

5.1 线程安全模型

shared_ptr的线程安全就像一场精心编排的舞蹈——每个人都知道自己的步骤,但还需要一些协调:

Thread1Thread2ControlBlockObject初始状态: ref_count=1拷贝构造fetch_add(1)ref_count=2拷贝构造fetch_add(1)ref_count=3对象访问需要外部同步!非同步访问🚫非同步访问🚫析构fetch_sub(1)旧计数=2析构fetch_sub(1)旧计数=1最后一个shared_ptr析构析构fetch_sub(1)旧计数=0删除对象自销毁Thread1Thread2ControlBlockObject

5.2 多线程最佳实践

#include <iostream>
#include <memory>
#include <thread>
#include <vector>class ThreadSafeLogger {
public:void log(const std::string& message) {// 需要外部同步!std::lock_guard<std::mutex> lock(mutex);std::cout << "[" << std::this_thread::get_id() << "] " << message << std::endl;}private:std::mutex mutex;
};void worker(std::shared_ptr<ThreadSafeLogger> logger, int id) {// shared_ptr的拷贝是线程安全的auto local_copy = logger;// 但对象访问需要同步local_copy->log("线程 " + std::to_string(id) + " 开始工作");// 模拟工作std::this_thread::sleep_for(std::chrono::milliseconds(100));local_copy->log("线程 " + std::to_string(id) + " 完成工作");
}int main() {auto logger = std::make_shared<ThreadSafeLogger>();std::vector<std::thread> threads;logger->log("主线程启动工作线程");// 创建多个工作线程for (int i = 0; i < 5; ++i) {threads.emplace_back(worker, logger, i);}// 等待所有线程完成for (auto& t : threads) {t.join();}logger->log("所有线程完成");return 0;
}

6. 🏆 总结与最佳实践

6.1 shared_ptr的优缺点总结

优点 👍:

  • ✅ 自动内存管理,告别内存泄漏
  • ✅ 共享所有权,灵活的资源管理
  • ✅ 线程安全的引用计数
  • ✅ 支持自定义删除器和分配器
  • ✅ 与weak_ptr配合避免循环引用

缺点 👎:

  • ❌ 性能开销(原子操作、控制块)
  • ❌ 潜在的循环引用问题
  • ❌ 控制块和对象可能分离存储
  • ❌ 不适用于所有场景

6.2 最佳实践指南

  1. 优先使用make_shared

    // 👍 好
    auto ptr = std::make_shared<MyClass>(args);// 👎 避免
    std::shared_ptr<MyClass> ptr(new MyClass(args));
    
  2. 参数传递优化

    // 👍 常量引用传递
    void func(const std::shared_ptr<MyClass>& ptr);// 👍 移动语义传递
    void func(std::shared_ptr<MyClass> ptr);
    
  3. 避免循环引用

    struct Node {std::shared_ptr<Node> next;std::weak_ptr<Node> prev;  // 使用weak_ptr!
    };
    
  4. 多线程注意事项

    // shared_ptr拷贝安全,但对象访问需要同步
    std::shared_ptr<Data> global_data;
    std::mutex data_mutex;void thread_func() {auto local_copy = global_data;  // 安全std::lock_guard<std::mutex> lock(data_mutex);  // 需要同步!local_copy->modify();
    }
    

6.3 性能优化技巧

场景推荐做法原因
频繁拷贝使用const引用传递减少原子操作次数
临时对象使用移动语义避免不必要的引用计数操作
性能敏感考虑unique_ptr更轻量级的解决方案
大量小对象使用make_shared减少内存分配次数

🎊 最后的最后

恭喜你!现在你已经从shared_ptr的"新手村"毕业,成为了内存管理的"魔法师"!记住:

“With great power comes great responsibility.”
—— 能力越大,责任越大

shared_ptr给了你强大的内存管理能力,但也要负责任地使用它。现在就去写些漂亮的代码吧!🚀

希望这篇指南让你笑着学会了shared_ptr的精髓!如果有任何问题,记得shared_ptr社区永远欢迎你!😊


文章转载自:

http://Um85Tyn0.mfnsn.cn
http://kIhDRr0L.mfnsn.cn
http://l9zLKimO.mfnsn.cn
http://mPkTYshn.mfnsn.cn
http://5jtOyn1B.mfnsn.cn
http://wU2y4sn2.mfnsn.cn
http://eMp7sfH9.mfnsn.cn
http://Je9OQ82M.mfnsn.cn
http://nHgflrRQ.mfnsn.cn
http://Q5Nj0bnf.mfnsn.cn
http://lIciAZ3c.mfnsn.cn
http://p0WRRXyL.mfnsn.cn
http://GiuvFm40.mfnsn.cn
http://KnL3IfkN.mfnsn.cn
http://LTQ9wgRC.mfnsn.cn
http://CX7CPnjk.mfnsn.cn
http://8j6H99Bo.mfnsn.cn
http://gz60xuhh.mfnsn.cn
http://39hcunPD.mfnsn.cn
http://UzSt8vcO.mfnsn.cn
http://o6tRft22.mfnsn.cn
http://UG6OKuCH.mfnsn.cn
http://swbtODYk.mfnsn.cn
http://hl81yVt4.mfnsn.cn
http://x2vRAIiR.mfnsn.cn
http://J0xn7NwW.mfnsn.cn
http://weRZM2jZ.mfnsn.cn
http://vMCctArj.mfnsn.cn
http://E38HpaVQ.mfnsn.cn
http://P11GKzov.mfnsn.cn
http://www.dtcms.com/a/384455.html

相关文章:

  • 计算机操作系统学习(四、文件管理)
  • Open3D-Geometry-15:UV Maps 将2D图像投影到3D模型表面
  • 从pip到UV:新一代包管理器的高效替代方案
  • 基于Matlab的雾霾天气和夜间车牌识别系统
  • 【Unity】高性能的事件分发系统
  • BM3D 图像降噪快速算法的 MATLAB 实现
  • 【pycharm】 ubuntu24.04 搭建uv环境
  • 科普:Python 的包管理工具:uv 与 pip
  • Golang语言入门篇002_安装Golang
  • cemu运行塞尔达传说:旷野之息的闪退问题以及解决方案记录
  • 【面试之Redis篇】主从复制原理
  • MySQL 8.0 在 Ubuntu 22.04 中如何将启用方式改为mysql_native_password(密码认证)
  • 轨道交通绝缘监测—轨道交通安全的隐形防线
  • Golang 语言中的函数类型
  • 《投资-54》数字资产的形式有哪些?
  • leetcode41(对称二叉树)
  • 链表详解:(后续会更新)
  • 光谱相机在半导体缺陷检测中的应用
  • 计算机组成原理-第一章
  • 修改 Windows 10 系统更新暂停天数指南
  • Flutter系统亮度检测完全指南:MediaQuery.platformBrightnessOf() 的妙用
  • flutter鸿蒙:适配app_links插件
  • 计算机视觉(opencv)实战二十二——指纹图像中提取特征点,计算两两指纹之间的相似度
  • 如何启动档案开启对话框及浏览资料夹对话框
  • 抗菌涂层与智能诊疗:伟荣医疗重构口腔器械感控与精准治疗新范式
  • python3
  • 茉莉 X4-QZ 840M矿机参数分析:Etchash算法挖矿的高效能选择
  • iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
  • 鸿蒙Next ArkWeb网页多媒体开发实战:从基础到高级应用
  • ActiveMQ RocketMQ RabbitMQ Kafka选型及应用场景