乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

shared_ptr 与 weak_ptr

定义与作用

std::shared_ptr(C++11)实现共享所有权——多个 shared_ptr 可指向同一对象,当最后一个 shared_ptr 被销毁时对象才被释放。底层通过引用计数管理生命周期。

std::weak_ptr(C++11)是"弱引用"——不增加引用计数,用于观察 shared_ptr 管理的对象和打破循环引用。

auto sp1 = std::make_shared<int>(42);  // 引用计数 = 1
auto sp2 = sp1;                        // 引用计数 = 2
std::weak_ptr<int> wp = sp1;          // 引用计数仍为 2
sp2.reset();                           // 引用计数 = 1

核心原理

shared_ptr 的控制块

weak_ptr 打破循环引用

完整示例

示例一:企业组织架构中的 shared_ptr

场景说明:企业部门管理——多个部门共享一份公司资源,weak_ptr 用于观察者模式。

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>

// 公司资源——被多个部门共享
class SharedResource {
public:
    explicit SharedResource(std::string name)
        : name_(std::move(name)) {
        std::cout << "  创建资源: " << name_ << std::endl;
    }
    ~SharedResource() {
        std::cout << "  销毁资源: " << name_ << std::endl;
    }
    const std::string& name() const { return name_; }
private:
    std::string name_;
};

// 部门——持有 shared_ptr 共享公司资源
class Department {
public:
    Department(std::string name, std::shared_ptr<SharedResource> res)
        : name_(std::move(name)), resource_(std::move(res)) {}

    void describe() const {
        std::cout << "  " << name_ << " 使用资源: "
                  << resource_->name()
                  << " (引用计数: " << resource_.use_count() << ")"
                  << std::endl;
    }

    std::shared_ptr<SharedResource> resource() const { return resource_; }

    const std::string& name() const { return name_; }

private:
    std::string name_;
    std::shared_ptr<SharedResource> resource_;
};

// 资源监视器——持有 weak_ptr 观察资源状态
class ResourceMonitor {
public:
    void watch(std::shared_ptr<SharedResource> res) {
        watched_.push_back(res);
    }

    void report() const {
        std::cout << "===== 资源监视报告 =====" << std::endl;
        for (const auto& wp : watched_) {
            if (auto sp = wp.lock()) {
                std::cout << "  活跃: " << sp->name()
                          << " (引用计数: " << sp.use_count() << ")"
                          << std::endl;
            } else {
                std::cout << "  已释放的资源" << std::endl;
            }
        }
    }

private:
    std::vector<std::weak_ptr<SharedResource>> watched_;
};

int main() {
    std::cout << "===== shared_ptr 共享资源 =====" << std::endl;

    // 创建一份共享资源
    auto database = std::make_shared<SharedResource>("企业数据库");
    std::cout << "初始引用计数: " << database.use_count() << std::endl;

    // 多个部门共享同一资源
    Department hr("人力资源部", database);
    Department it("信息技术部", database);
    Department finance("财务部", database);

    std::cout << "\n各部门持有后引用计数: " << database.use_count() << std::endl;
    hr.describe();
    it.describe();
    finance.describe();

    // 监视器观察资源
    ResourceMonitor monitor;
    monitor.watch(database);
    monitor.report();

    std::cout << "\n--- 释放主引用 ---" << std::endl;
    database.reset();  // main 放弃引用
    std::cout << "主引用释放后:" << std::endl;
    hr.describe();

    std::cout << "\n--- 所有部门释放 ---" << std::endl;
    // hr, it, finance 将在 main 结束时析构
    // 离开作用域 → 3 个 Department 析构 → 所有 shared_ptr 销毁 → 资源释放

    return 0;
}

预期输出:

===== shared_ptr 共享资源 =====
  创建资源: 企业数据库
初始引用计数: 1

各部门持有后引用计数: 4
  人力资源部 使用资源: 企业数据库 (引用计数: 4)
  信息技术部 使用资源: 企业数据库 (引用计数: 4)
  财务部 使用资源: 企业数据库 (引用计数: 4)
===== 资源监视报告 =====
  活跃: 企业数据库 (引用计数: 4)

--- 释放主引用 ---
主引用释放后:
  人力资源部 使用资源: 企业数据库 (引用计数: 3)

--- 所有部门释放 ---
  销毁资源: 企业数据库

逐段分析:

  • std::make_shared<T>() 一次分配对象 + 控制块——比 shared_ptr<T>(new T) 更高效
  • use_count() 返回当前强引用计数——调试友好
  • 多个 Department 持有同一份 shared_ptr——每个拷贝增加引用计数
  • ResourceMonitor 用 weak_ptr 观察——不增加引用计数,不影响资源生命周期
  • wp.lock() 尝试提升为 shared_ptr——如果资源已释放返回空

示例二:双向关系中 weak_ptr 打破循环引用

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Person;  // 前向声明

// 使用 weak_ptr 避免循环引用
class Person {
public:
    Person(std::string name) : name_(std::move(name)) {
        std::cout << "  构造: " << name_ << std::endl;
    }

    ~Person() {
        std::cout << "  析构: " << name_ << std::endl;
    }

    // 设置配偶(双向关联)
    void setSpouse(std::shared_ptr<Person> spouse) {
        spouse_ = spouse;  // weak_ptr——不增加引用计数
    }

    // 添加朋友
    void addFriend(std::shared_ptr<Person> friend_) {
        friends_.push_back(friend_);
    }

    void describe() const {
        std::cout << "  " << name_;
        if (auto sp = spouse_.lock()) {
            std::cout << ",配偶: " << sp->name_;
        } else {
            std::cout << ",未婚";
        }
        std::cout << ",朋友: ";
        for (const auto& wp : friends_) {
            if (auto sp = wp.lock())
                std::cout << sp->name_ << " ";
        }
        std::cout << std::endl;
    }

    const std::string& name() const { return name_; }

private:
    std::string name_;
    std::weak_ptr<Person> spouse_;              // weak_ptr 而非 shared_ptr
    std::vector<std::weak_ptr<Person>> friends_; // weak_ptr 观察朋友
};

int main() {
    std::cout << "===== weak_ptr 打破循环引用 =====" << std::endl;

    {
        auto alice = std::make_shared<Person>("Alice");
        auto bob = std::make_shared<Person>("Bob");

        // 双向关联
        alice->setSpouse(bob);
        bob->setSpouse(alice);

        alice->addFriend(bob);
        bob->addFriend(alice);

        alice->describe();
        bob->describe();

        std::cout << "\nAlice ref count: " << alice.use_count() << std::endl;
        std::cout << "Bob ref count: " << bob.use_count() << std::endl;

        // 如果 spouse_ 用 shared_ptr,引用计数相互加 1,离开作用域双方都不会被释放
        // weak_ptr 避免了这个问题
    }

    std::cout << "\n--- 离开作用域后 ---" << std::endl;
    std::cout << "Alice 和 Bob 都被正确释放" << std::endl;

    return 0;
}

预期输出:

===== weak_ptr 打破循环引用 =====
  构造: Alice
  构造: Bob
  Alice,配偶: Bob,朋友: Bob 
  Bob,配偶: Alice,朋友: Alice 

Alice ref count: 1
Bob ref count: 1
  析构: Bob
  析构: Alice

--- 离开作用域后 ---
Alice 和 Bob 都被正确释放

逐段分析:

  • 如果把 spouse_ 定义为 shared_ptr<Person>,Alice 和 Bob 互相持有对方的 shared_ptr → 引用计数均为 2(alice + bob.spouse_ / bob + alice.spouse_)
  • 离开作用域时,alice 和 bob 析构 → 引用计数各减 1 → 剩余引用计数 = 1(对方的 spouse_)→ 两者永不释放 → 内存泄漏
  • 使用 weak_ptr 后,spouse_ 不增加引用计数 → 离开作用域后两人引用计数均为 0 → 正确释放
  • friends_ 同样用 weak_ptr——朋友关系是弱关联,不应阻止任何人被释放

易错场景与面试考点

易错场景

场景错误表现正确做法
循环引用(两个 shared_ptr 互相持有)内存泄漏一方改用 weak_ptr
shared_ptr(this) 在成员函数中多个控制块 → 双重释放继承 std::enable_shared_from_this
shared_ptr<T>(new T) 用在函数参数函数参数求值顺序不确定 → 泄漏用 make_shared<T>()
weak_ptr 不检查就直接解引用未定义行为先 lock() 检查是否为空
在多线程中修改 shared_ptr数据竞争用 std::atomic_...<shared_ptr> 或加锁

常见面试问题

  1. shared_ptr 的引用计数是线程安全的吗?——引用计数的增减是线程安全的(原子操作),但 shared_ptr 对象本身的赋值和拷贝不是线程安全的。多线程读写同一个 shared_ptr 需要同步。

  2. make_shared 和 shared_ptr(new T) 的区别?——make_shared 一次分配对象 + 控制块(内存连续、性能好);shared_ptr(new T) 至少两次分配。make_shared 异常安全——函数参数求值顺序不会导致泄漏。

  3. weak_ptr 的作用?——(1) 打破循环引用;(2) 缓存/观察者模式——不阻止对象释放;(3) lock() 安全地检查对象是否仍存活。

  4. enable_shared_from_this 是做什么的?——让从 this 安全获取 shared_ptr 成为可能。内部用 weak_ptr 保存控制块信息,shared_from_this() 返回引用同一控制块的 shared_ptr。

  5. shared_ptr 的删除器存储在哪里?何时调用?——存储在控制块中。当引用计数降为 0 时调用删除器释放对象;当弱引用计数也降为 0 时释放控制块。

小结

  • shared_ptr:共享所有权 + 引用计数——最后一个释放时 delete 对象
  • make_shared:一次分配对象 + 控制块——性能更好,异常安全
  • weak_ptr:弱引用——不增加引用计数,lock() 检查对象存活
  • 循环引用是 shared_ptr 的致命陷阱——用 weak_ptr 切断循环的一环
  • enable_shared_from_this:从 this 安全获取 shared_ptr——避免多控制块问题
上一页
unique_ptr
下一页
内存管理最佳实践