• 文件浏览器
  • 001 《C++语言纲要:从入门到精通》 002 《C++ 整数类型深度解析:从基础到高级》 003 《C++ 字符类型深度解析:从入门到精通 (C++ Character Types: In-depth Analysis from Beginner to Expert)》 004 《C++ 浮点类型深度解析 (In-Depth Analysis of C++ Floating-Point Types)》 005 《深入理解C++布尔类型(A Deep Dive into C++ Boolean Type)》 006 《C++ 空类型 (Void Type) 深度解析:从基础到高级应用》 007 《C++ nullptr_t 类型全面深度解析》 008 《C++ auto 类型深度解析:从入门到精通 (In-depth Analysis of C++ auto Type: From Beginner to Expert)》 009 《C++原始指针(Raw Pointer)深度解析》 010 《C++ 数组 (Array) 全面深度解析》 011 《C++ 引用类型 (Reference Type) 深度解析》 012 《C++ std::string 全面深度解析与实践》 013 《深入探索 folly::string:高效字符串处理的艺术与实践》 014 《C++ unique_ptr 深度解析:从入门到精通》 015 《C++ shared_ptr 深度解析:原理、应用与最佳实践 (C++ shared_ptr Deep Dive: Principles, Applications, and Best Practices)》 016 《C++ weak_ptr 深度解析:原理、应用与最佳实践》 017 《C++ 函数深度解析 (Deep Dive into C++ Functions)》 018 《C++ 枚举类型 (enum 和 enum class) 全面深度解析》 019 《C++ Union 类型全面深度解析》 020 《C++ 结构体 (struct) 全面深度解析》 021 《C++ 类全面深度解析》 022 《C++ 面向对象技术深度解析与实践》 023 《C++设计模式:原理、实践与现代应用》 024 《C++关键词深度解析:从基础到现代C++》

    015 《C++ shared_ptr 深度解析:原理、应用与最佳实践 (C++ shared_ptr Deep Dive: Principles, Applications, and Best Practices)》


    作者Lou Xiao, gemini创建时间2025-04-22 20:19:41更新时间2025-04-22 20:19:41

    🌟🌟🌟本文由Gemini 2.0 Flash Thinking Experimental 01-21生成,用来辅助学习。🌟🌟🌟

    书籍大纲

    ▮▮ 1. 智能指针概览 (Overview of Smart Pointers)
    ▮▮▮▮ 1.1 为什么需要智能指针 (Why Smart Pointers are Needed)
    ▮▮▮▮▮▮ 1.1.1 手动内存管理的挑战 (Challenges of Manual Memory Management)
    ▮▮▮▮▮▮ 1.1.2 RAII 原则 (RAII Principle)
    ▮▮▮▮ 1.2 智能指针的分类 (Types of Smart Pointers)
    ▮▮▮▮▮▮ 1.2.1 独占所有权:unique_ptr (Exclusive Ownership: unique_ptr)
    ▮▮▮▮▮▮ 1.2.2 共享所有权:shared_ptr (Shared Ownership: shared_ptr)
    ▮▮▮▮▮▮ 1.2.3 弱引用:weak_ptr (Weak Reference: weak_ptr)
    ▮▮ 2. shared_ptr 基础 (shared_ptr Basics)
    ▮▮▮▮ 2.1 shared_ptr 的基本概念 (Basic Concepts of shared_ptr)
    ▮▮▮▮▮▮ 2.1.1 引用计数 (Reference Counting)
    ▮▮▮▮▮▮ 2.1.2 共享所有权 (Shared Ownership)
    ▮▮▮▮ 2.2 shared_ptr 的创建与初始化 (Creation and Initialization of shared_ptr)
    ▮▮▮▮▮▮ 2.2.1 使用 make_shared (Using make_shared)
    ▮▮▮▮▮▮ 2.2.2 构造函数 (Constructors)
    ▮▮▮▮▮▮ 2.2.3 赋值操作 (Assignment Operations)
    ▮▮▮▮ 2.3 shared_ptr 的使用 (Usage of shared_ptr)
    ▮▮▮▮▮▮ 2.3.1 解引用操作符 () (Dereference Operator ())
    ▮▮▮▮▮▮ 2.3.2 箭头操作符 (->) (Arrow Operator (->))
    ▮▮▮▮▮▮ 2.3.3 get() 方法 (get() Method)
    ▮▮▮▮ 2.4 shared_ptr 的销毁与析构 (Destruction and Destructor of shared_ptr)
    ▮▮▮▮▮▮ 2.4.1 析构函数的调用时机 (When Destructor is Called)
    ▮▮▮▮▮▮ 2.4.2 资源释放 (Resource Release)
    ▮▮ 3. shared_ptr 深度解析 (In-depth Analysis of shared_ptr)
    ▮▮▮▮ 3.1 引用计数的原理与实现 (Principle and Implementation of Reference Counting)
    ▮▮▮▮▮▮ 3.1.1 原子操作 (Atomic Operations)
    ▮▮▮▮▮▮ 3.1.2 引用计数的内部实现 (Internal Implementation of Reference Counting)
    ▮▮▮▮ 3.2 控制块 (Control Block)
    ▮▮▮▮▮▮ 3.2.1 控制块的结构 (Structure of Control Block)
    ▮▮▮▮▮▮ 3.2.2 控制块的生命周期 (Lifecycle of Control Block)
    ▮▮▮▮ 3.3 线程安全性 (Thread Safety)
    ▮▮▮▮▮▮ 3.3.1 多线程环境下的 shared_ptr (shared_ptr in Multi-threaded Environments)
    ▮▮▮▮▮▮ 3.3.2 线程安全的操作 (Thread-safe Operations)
    ▮▮▮▮ 3.4 自定义删除器 (Custom Deleters)
    ▮▮▮▮▮▮ 3.4.1 函数对象作为删除器 (Function Objects as Deleters)
    ▮▮▮▮▮▮ 3.4.2 Lambda 表达式作为删除器 (Lambda Expressions as Deleters)
    ▮▮▮▮▮▮ 3.4.3 删除器与类型擦除 (Deleters and Type Erasure)
    ▮▮▮▮ 3.5 别名 shared_ptr (Aliasing shared_ptr)
    ▮▮▮▮▮▮ 3.5.1 别名构造函数 (Aliasing Constructor)
    ▮▮▮▮▮▮ 3.5.2 应用场景 (Use Cases)
    ▮▮ 4. shared_ptr 的高级应用 (Advanced Applications of shared_ptr)
    ▮▮▮▮ 4.1 shared_ptr 与容器 (shared_ptr and Containers)
    ▮▮▮▮▮▮ 4.1.1 在容器中存储 shared_ptr (Storing shared_ptr in Containers)
    ▮▮▮▮▮▮ 4.1.2 容器与所有权转移 (Containers and Ownership Transfer)
    ▮▮▮▮ 4.2 shared_ptr 与多态 (shared_ptr and Polymorphism)
    ▮▮▮▮▮▮ 4.2.1 虚析构函数的重要性 (Importance of Virtual Destructors)
    ▮▮▮▮▮▮ 4.2.2 多态删除 (Polymorphic Deletion)
    ▮▮▮▮ 4.3 工厂模式与 shared_ptr (Factory Pattern and shared_ptr)
    ▮▮▮▮▮▮ 4.3.1 工厂函数返回 shared_ptr (Factory Functions Returning shared_ptr)
    ▮▮▮▮▮▮ 4.3.2 简化对象生命周期管理 (Simplifying Object Lifetime Management)
    ▮▮▮▮ 4.4 Pimpl 惯用法与 shared_ptr (Pimpl Idiom and shared_ptr)
    ▮▮▮▮▮▮ 4.4.1 使用 shared_ptr 实现 Pimpl (Implementing Pimpl with shared_ptr)
    ▮▮▮▮▮▮ 4.4.2 优势与注意事项 (Advantages and Considerations)
    ▮▮ 5. shared_ptr 与 weak_ptr、unique_ptr 的比较 (Comparison of shared_ptr with weak_ptr and unique_ptr)
    ▮▮▮▮ 5.1 weak_ptr 的作用与使用场景 (Role and Use Cases of weak_ptr)
    ▮▮▮▮▮▮ 5.1.1 解决循环引用 (Resolving Circular Dependencies)
    ▮▮▮▮▮▮ 5.1.2 观察者模式 (Observer Pattern)
    ▮▮▮▮▮▮ 5.1.3 weak_ptr 的使用方法 (How to Use weak_ptr)
    ▮▮▮▮ 5.2 unique_ptr 的作用与使用场景 (Role and Use Cases of unique_ptr)
    ▮▮▮▮▮▮ 5.2.1 独占所有权 (Exclusive Ownership)
    ▮▮▮▮▮▮ 5.2.2 移动语义与 unique_ptr (Move Semantics and unique_ptr)
    ▮▮▮▮▮▮ 5.2.3 unique_ptr 的使用方法 (How to Use unique_ptr)
    ▮▮▮▮ 5.3 何时使用 shared_ptr, weak_ptr, unique_ptr (When to Use shared_ptr, weak_ptr, unique_ptr)
    ▮▮▮▮▮▮ 5.3.1 选择合适的智能指针 (Choosing the Right Smart Pointer)
    ▮▮▮▮▮▮ 5.3.2 最佳实践总结 (Summary of Best Practices)
    ▮▮ 6. shared_ptr 的陷阱与最佳实践 (Pitfalls and Best Practices of shared_ptr)
    ▮▮▮▮ 6.1 循环引用问题与解决方案 (Circular Dependency Issues and Solutions)
    ▮▮▮▮▮▮ 6.1.1 循环引用的成因 (Causes of Circular References)
    ▮▮▮▮▮▮ 6.1.2 使用 weak_ptr 打破循环引用 (Using weak_ptr to Break Circular References)
    ▮▮▮▮ 6.2 性能考量 (Performance Considerations)
    ▮▮▮▮▮▮ 6.2.1 引用计数的开销 (Overhead of Reference Counting)
    ▮▮▮▮▮▮ 6.2.2 性能优化技巧 (Performance Optimization Tips)
    ▮▮▮▮ 6.3 避免裸指针与 new/delete (Avoiding Raw Pointers and new/delete)
    ▮▮▮▮▮▮ 6.3.1 现代 C++ 资源管理原则 (Modern C++ Resource Management Principles)
    ▮▮▮▮▮▮ 6.3.2 代码示例与重构 (Code Examples and Refactoring)
    ▮▮▮▮ 6.4 代码审查与 shared_ptr 的使用规范 (Code Review and Usage Guidelines of shared_ptr)
    ▮▮▮▮▮▮ 6.4.1 代码审查checklist (Code Review Checklist)
    ▮▮▮▮▮▮ 6.4.2 团队开发规范 (Team Development Guidelines)
    ▮▮ 附录A: 参考文献 (References)
    ▮▮ 附录B: 术语表 (Glossary)
    ▮▮ 附录C: shared_ptr 相关面试题 (Interview Questions Related to shared_ptr)


    1. 智能指针概览 (Overview of Smart Pointers)

    1.1 为什么需要智能指针 (Why Smart Pointers are Needed)

    1.1.1 手动内存管理的挑战 (Challenges of Manual Memory Management)

    在传统的 C++ 编程中,动态内存管理是一项核心但又充满挑战的任务。程序员通过 new 表达式在堆 (heap) 上分配内存,并使用 delete 表达式显式地释放不再需要的内存。这种手动内存管理 (manual memory management) 方式赋予了开发者极大的灵活性,但也引入了诸多潜在的风险和问题。

    内存泄漏 (Memory Leaks)
    这是手动内存管理中最常见的问题之一。当使用 new 分配的内存没有被 delete 正确释放时,就会发生内存泄漏。长期运行的程序中,持续的内存泄漏会逐渐消耗系统资源,最终可能导致程序性能下降甚至崩溃。例如,以下代码片段展示了一个简单的内存泄漏场景:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void foo() {
    2 int* ptr = new int(42);
    3 // ... 假设这里因为某种原因函数提前返回,例如抛出异常
    4 return;
    5 delete ptr; // 这行代码可能不会被执行到
    6 }

    如果在 // ... 处因为异常或其他控制流导致函数提前返回,delete ptr; 这行代码将不会被执行,从而造成内存泄漏。

    悬挂指针 (Dangling Pointers)
    当一个指针指向的内存已经被释放之后,如果程序仍然尝试访问这块内存,就会产生悬挂指针。悬挂指针的解引用会导致未定义行为 (undefined behavior),这是一种非常危险的情况,可能导致程序崩溃、数据损坏,或者更糟糕的是,表面上看起来程序还能继续运行,但结果却是不可预测的。例如:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 int* ptr = new int(10);
    2 int* danglingPtr = ptr;
    3 delete ptr;
    4 *danglingPtr = 20; // 悬挂指针解引用,未定义行为

    在上述代码中,ptr 指向的内存被 delete 释放后,danglingPtr 就变成了悬挂指针。尝试通过 danglingPtr 修改内存会导致未定义行为。

    重复释放 (Double Free)
    错误地多次释放同一块内存也是一个常见的问题。重复释放会导致堆 (heap) 管理器的内部数据结构损坏,通常会立即导致程序崩溃。例如:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 int* ptr = new int(5);
    2 delete ptr;
    3 delete ptr; // 重复释放,程序崩溃

    这段代码中,ptr 指向的内存被 delete 释放了两次,这将导致程序运行时错误。

    异常安全 (Exception Safety) 问题:
    在 C++ 中,异常处理是处理错误和异常情况的重要机制。然而,手动内存管理与异常处理结合时,更容易出现问题。如果在 newdelete 之间,或者在分配内存后但在释放内存前抛出异常,就可能导致资源泄漏。如同内存泄漏的例子所示。

    代码复杂性和维护性 (Code Complexity and Maintainability)
    手动内存管理增加了代码的复杂性,使得代码更难编写、阅读和维护。程序员需要时刻关注内存的分配和释放,这分散了他们在业务逻辑上的注意力,并且容易引入错误。尤其是在大型项目中,手动内存管理的代码会变得散布在各处,难以跟踪和管理。

    为了解决上述手动内存管理带来的挑战,现代 C++ 引入了智能指针 (smart pointers) 的概念。智能指针本质上是RAII (Resource Acquisition Is Initialization) 思想的具体应用,它们通过封装原始指针,并在对象生命周期结束时自动释放所管理的资源,从而极大地简化了内存管理,提高了程序的安全性、可靠性和可维护性。智能指针能够有效地避免内存泄漏、悬挂指针和重复释放等问题,并使代码更清晰、更专注于业务逻辑。

    1.1.2 RAII 原则 (RAII Principle)

    RAII (Resource Acquisition Is Initialization),即资源获取即初始化,是 C++ 中进行资源管理的核心原则。它是由 Bjarne Stroustrup 提出的,旨在解决资源管理,特别是内存管理中的各种问题,例如内存泄漏、资源泄露以及异常安全问题。RAII 的核心思想是将资源的生命周期与对象的生命周期绑定在一起,用对象的构造来获取资源,用对象的析构来释放资源。

    资源与对象生命周期绑定 (Resource-to-Object Lifecycle Binding)
    RAII 的关键在于将资源的获取和释放操作分别放在对象的构造函数 (constructor)析构函数 (destructor) 中。当对象被创建时,构造函数负责获取资源(例如,分配内存、打开文件、获取锁等);当对象生命周期结束时(例如,对象离开作用域、被显式删除等),析构函数自动被调用,负责释放之前获取的资源(例如,释放内存、关闭文件、释放锁等)。

    自动资源管理 (Automatic Resource Management)
    由于 C++ 保证在对象生命周期结束时会自动调用析构函数(即使是在发生异常的情况下,通过栈展开 (stack unwinding) 机制也能保证析构函数被调用),因此,通过 RAII 原则,资源的释放也变得自动化。程序员不再需要显式地调用释放资源的代码,从而大大降低了资源泄漏的风险。

    异常安全 (Exception Safety)
    RAII 是实现异常安全编程的关键技术之一。在传统的资源管理方式中,如果在资源获取和资源释放之间发生异常,就可能导致资源无法被正确释放。而 RAII 机制确保了无论是否发生异常,只要对象生命周期结束,析构函数就一定会执行,从而保证资源得到及时释放。这极大地提高了程序的健壮性和可靠性。

    简化资源管理 (Simplified Resource Management)
    RAII 原则将复杂的资源管理逻辑封装在类的构造函数和析构函数中,使得资源的管理变得更加简单和直观。程序员只需要关注对象的创建和销毁,而无需手动跟踪和释放资源。这降低了代码的复杂性,提高了开发效率和代码的可维护性。

    RAII 的一般实现模式:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class ResourceWrapper {
    2 private:
    3 ResourceType* resourcePtr; // 管理的资源
    4
    5 public:
    6 // 构造函数:获取资源
    7 ResourceWrapper() : resourcePtr(acquireResource()) {
    8 if (!resourcePtr) {
    9 // 资源获取失败,抛出异常或进行错误处理
    10 throw std::runtime_error("Failed to acquire resource");
    11 }
    12 }
    13
    14 // 析构函数:释放资源
    15 ~ResourceWrapper() {
    16 releaseResource(resourcePtr);
    17 resourcePtr = nullptr;
    18 }
    19
    20 // ... 其他操作资源的方法
    21
    22 private:
    23 ResourceType* acquireResource() {
    24 // 获取资源的具体实现,例如 new 操作,打开文件等
    25 return new ResourceType();
    26 }
    27
    28 void releaseResource(ResourceType* res) {
    29 // 释放资源的具体实现,例如 delete 操作,关闭文件等
    30 delete res;
    31 }
    32
    33 // 禁用拷贝构造函数和拷贝赋值运算符,防止资源被错误共享和管理
    34 ResourceWrapper(const ResourceWrapper&) = delete;
    35 ResourceWrapper& operator=(const ResourceWrapper&) = delete;
    36 };
    37
    38 void usingResource() {
    39 ResourceWrapper wrapper; // 创建 ResourceWrapper 对象,资源在构造函数中获取
    40 // ... 使用资源
    41 } // wrapper 对象生命周期结束,析构函数自动调用,资源被释放

    在上述代码示例中,ResourceWrapper 类封装了资源的获取和释放逻辑。当 wrapper 对象在 usingResource 函数中创建时,构造函数会获取资源;当 wrapper 对象生命周期结束时(函数 usingResource 执行完毕),析构函数会自动释放资源。即使在 // ... 使用资源 的过程中抛出异常,析构函数仍然会被调用,保证了资源的正确释放。

    智能指针正是 RAII 原则的典型应用。例如,std::shared_ptrstd::unique_ptrstd::weak_ptr 都是通过 RAII 机制来管理动态分配的内存,确保内存的自动释放,从而避免内存泄漏和悬挂指针等问题。智能指针的出现极大地简化了 C++ 中的内存管理,使得程序员可以更专注于业务逻辑的实现,同时编写出更安全、更可靠的代码。

    1.2 智能指针的分类 (Types of Smart Pointers)

    C++11 标准引入了智能指针,作为现代 C++ 内存管理的重要组成部分。智能指针是实现了 RAII 原则的类模板,用于自动管理动态分配的内存。C++11 标准库提供了三种主要的智能指针类型,每种类型都有不同的所有权语义和使用场景:unique_ptrshared_ptrweak_ptr

    1.2.1 独占所有权:unique_ptr (Exclusive Ownership: unique_ptr)

    std::unique_ptr 提供了独占所有权 (exclusive ownership) 的语义。这意味着一个 unique_ptr 对象独占地拥有它所指向的对象。在同一时刻,只能有一个 unique_ptr 指向特定的对象。当 unique_ptr 对象被销毁时,它所拥有的对象也会被自动删除。这使得 unique_ptr 非常适合管理生命周期唯一的对象,例如函数内部动态分配的临时对象或者类成员变量。

    独占性 (Exclusivity)
    unique_ptr 的核心特点是独占性。它保证了对资源的唯一所有权。不能通过拷贝构造函数 (copy constructor)拷贝赋值运算符 (copy assignment operator) 来复制 unique_ptr 对象。这防止了多个 unique_ptr 对象同时管理同一块内存,从而避免了潜在的资源管理冲突和错误。

    移动语义 (Move Semantics)
    虽然 unique_ptr 不支持拷贝,但它支持移动 (move) 操作。通过 std::move 函数,可以将所有权从一个 unique_ptr 对象转移到另一个 unique_ptr 对象。移动操作会将源 unique_ptr 对象置为空,并将资源的所有权转移给目标 unique_ptr 对象。移动语义使得 unique_ptr 可以用于函数返回值和容器中,而不会产生额外的性能开销。

    自动删除 (Automatic Deletion)
    unique_ptr 对象超出作用域或被显式销毁时,其析构函数会自动调用 delete 运算符来释放所管理的内存。这确保了动态分配的内存在不再需要时会被及时释放,从而避免内存泄漏。

    自定义删除器 (Custom Deleters)
    unique_ptr 允许用户指定自定义删除器 (custom deleter)。自定义删除器是一个函数对象(可以是函数指针、Lambda 表达式或重载了函数调用运算符 operator() 的类),当 unique_ptr 销毁所管理的资源时,会调用用户提供的删除器,而不是默认的 delete 运算符。这使得 unique_ptr 可以管理各种类型的资源,例如文件句柄、网络连接等,而不仅仅是动态分配的内存。

    unique_ptr 的典型应用场景:

    函数返回动态分配的对象

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::unique_ptr<int> createUniqueInt(int value) {
    2 return std::unique_ptr<int>(new int(value));
    3 }
    4
    5 void processInt() {
    6 std::unique_ptr<int> ptr = createUniqueInt(100);
    7 // ... 使用 ptr
    8 } // ptr 超出作用域,所管理的 int 对象自动释放

    createUniqueInt 函数返回一个 unique_ptr,它拥有新分配的 int 对象的独占所有权。当 ptrprocessInt 函数结束时超出作用域,unique_ptr 的析构函数会自动释放 int 对象。

    类成员变量,管理独占拥有的对象

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class MyClass {
    2 private:
    3 std::unique_ptr<Resource> resourcePtr;
    4 public:
    5 MyClass() : resourcePtr(new Resource()) {}
    6 // ...
    7 }; // MyClass 对象销毁时,resourcePtr 所管理的 Resource 对象也自动释放

    MyClass 使用 unique_ptr 成员 resourcePtr 来管理一个 Resource 对象。当 MyClass 对象被销毁时,resourcePtr 会自动释放 Resource 对象。

    代替裸指针,提高代码安全性
    在需要使用指针管理动态分配内存的场景中,尽可能使用 unique_ptr 代替裸指针。unique_ptr 能够自动管理内存,避免手动 delete 带来的风险。

    总结: unique_ptr 是一种轻量级、高效的智能指针,提供了独占所有权的语义,适用于管理生命周期唯一、不需要共享的对象。它通过移动语义支持所有权的转移,并允许自定义删除器,使其能够灵活地管理各种类型的资源。在现代 C++ 编程中,unique_ptr 是管理独占资源的首选智能指针。

    1.2.2 共享所有权:shared_ptr (Shared Ownership: shared_ptr)

    std::shared_ptr 提供了共享所有权 (shared ownership) 的语义。多个 shared_ptr 对象可以共享同一个对象的所有权。shared_ptr 使用引用计数 (reference counting) 来跟踪有多少个 shared_ptr 正在指向同一个对象。只有当最后一个指向该对象的 shared_ptr 被销毁时,才会真正删除所管理的对象。这使得 shared_ptr 非常适合在多个组件之间共享资源所有权,例如在复杂的数据结构、对象缓存、事件处理系统中。

    共享所有权 (Shared Ownership)
    shared_ptr 的核心特点是共享所有权。多个 shared_ptr 对象可以同时指向同一个对象,并且它们之间共享所有权。可以通过拷贝构造函数、拷贝赋值运算符或者移动操作来创建指向同一对象的多个 shared_ptr 实例。

    引用计数 (Reference Counting)
    shared_ptr 使用引用计数来管理所拥有对象的生命周期。每个 shared_ptr 都关联一个控制块 (control block),控制块中维护着被共享对象的引用计数 (reference count)。当创建一个新的 shared_ptr 指向同一个对象时,引用计数会增加;当一个 shared_ptr 对象被销毁时,引用计数会减少。只有当引用计数降为零时,shared_ptr 才会真正删除所管理的对象。

    自动删除 (Automatic Deletion)
    当与某个对象关联的引用计数降为零时,最后一个 shared_ptr 对象的析构函数会自动调用删除器(默认是 delete 运算符)来释放所管理的内存。这确保了共享对象在不再被任何 shared_ptr 引用时会被自动释放,避免内存泄漏。

    自定义删除器 (Custom Deleters)
    unique_ptr 类似,shared_ptr 也允许用户指定自定义删除器。自定义删除器会在引用计数降为零,且需要删除所管理的对象时被调用。这使得 shared_ptr 可以管理各种类型的共享资源。

    线程安全 (Thread Safety)
    shared_ptr 的引用计数操作是原子 (atomic) 的,因此在多线程环境下,多个线程可以安全地共享和操作同一个 shared_ptr 对象,而无需额外的同步措施。但是,需要注意的是,虽然引用计数操作是线程安全的,但通过 shared_ptr 访问和修改所指向的对象本身不是线程安全的。如果多个线程同时访问或修改 shared_ptr 指向的共享对象,仍然需要进行适当的同步控制。

    shared_ptr 的典型应用场景:

    多个对象共享资源所有权

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 void observeObject(std::shared_ptr<int> sharedPtr) {
    5 std::cout << "引用计数: " << sharedPtr.use_count() << std::endl;
    6 }
    7
    8 int main() {
    9 std::shared_ptr<int> ptr1(new int(100));
    10 std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    11
    12 std::shared_ptr<int> ptr2 = ptr1; // 拷贝构造,ptr1 和 ptr2 共享所有权
    13 std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    14 std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl; // 输出 2
    15
    16 observeObject(ptr1); // 函数传参,ptr1 和函数内部的 sharedPtr 共享所有权,函数结束后 sharedPtr 销毁
    17 std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    18
    19 ptr1.reset(); // ptr1 不再拥有对象,引用计数减 1
    20 std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl; // 输出 1
    21
    22 ptr2.reset(); // ptr2 也不再拥有对象,引用计数减为 0,对象被释放
    23 std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl; // 输出 0 (或者程序结束,ptr2 销毁)
    24
    25 return 0;
    26 }

    在这个例子中,ptr1ptr2 共享同一个 int 对象的所有权。通过 use_count() 方法可以查看当前对象的引用计数。当 ptr1ptr2 都被 reset() 或超出作用域后,int 对象才会被释放。

    循环数据结构,例如双向链表、图
    在实现循环数据结构时,例如双向链表或图,节点之间可能需要相互引用。使用裸指针可能会导致难以管理的生命周期和内存泄漏风险。shared_ptr 可以用来管理节点之间的共享引用,但需要注意循环引用 (circular reference) 的问题,循环引用会导致引用计数永远无法降为零,从而造成内存泄漏。为了解决循环引用问题,通常会结合 weak_ptr 使用。

    工厂模式 (Factory Pattern)
    工厂函数可以使用 shared_ptr 返回动态创建的对象,将对象的所有权转移给调用者,同时允许多个调用者共享同一个对象。

    缓存 (Cache)
    在缓存系统中,多个缓存项可能需要共享同一个底层资源。shared_ptr 可以用于管理缓存项和底层资源之间的共享关系。

    总结: shared_ptr 是一种功能强大且用途广泛的智能指针,提供了共享所有权的语义,适用于需要在多个组件之间共享资源所有权的场景。它通过引用计数自动管理对象的生命周期,并支持自定义删除器和线程安全操作。然而,使用 shared_ptr 时需要注意循环引用问题,并合理选择是否使用 weak_ptr 来打破循环引用。在现代 C++ 编程中,shared_ptr 是实现共享资源管理的重要工具。

    1.2.3 弱引用:weak_ptr (Weak Reference: weak_ptr)

    std::weak_ptr 是一种弱引用 (weak reference) 智能指针。weak_ptr 不参与共享所有权,它只是观察shared_ptr 管理的对象,而不会增加对象的引用计数。weak_ptr 的主要目的是打破循环引用,以及在不增加对象生命周期的情况下访问 shared_ptr 所管理的对象。weak_ptr 必须从一个 shared_ptr 或另一个 weak_ptr 构造而来,并且不能直接解引用。要访问 weak_ptr 观察的对象,需要先调用 lock() 方法尝试将其转换为 shared_ptr

    非拥有性 (Non-owning)
    weak_ptr 不拥有它所指向的对象的所有权。创建 weak_ptr 不会增加对象的引用计数,销毁 weak_ptr 也不会减少对象的引用计数。weak_ptr 仅仅是提供了一种观察shared_ptr 管理的对象的方式,而不会影响对象的生命周期。

    打破循环引用 (Breaking Circular References)
    weak_ptr 最重要的用途是打破 shared_ptr 造成的循环引用。当两个或多个对象之间通过 shared_ptr 相互引用时,会形成循环引用,导致引用计数永远无法降为零,从而造成内存泄漏。将循环引用关系中的一个或多个 shared_ptr 改为 weak_ptr,就可以打破循环引用,使得对象能够被正确释放。

    观察对象状态 (Observing Object Status)
    weak_ptr 可以用来检查所观察的对象是否仍然存活。由于 weak_ptr 不参与所有权管理,当最后一个 shared_ptr 销毁时,即使还存在指向该对象的 weak_ptr,对象也会被释放。可以通过 weak_ptrexpired() 方法来检查对象是否已经被释放;也可以通过 lock() 方法尝试获取一个指向对象的 shared_ptr。如果对象已经被释放,lock() 方法会返回空的 shared_ptr

    从 shared_ptr 构建 (Construction from shared_ptr)
    weak_ptr 只能从 shared_ptr 或另一个 weak_ptr 构造而来。不能直接从裸指针构造 weak_ptr。这保证了 weak_ptr 总是观察由 shared_ptr 管理的对象。

    lock() 方法 (lock() Method)
    weak_ptr 本身不能直接访问所观察的对象。要访问对象,需要调用 weak_ptrlock() 方法。lock() 方法会尝试创建一个新的 shared_ptr 指向被观察的对象。如果对象仍然存活(即引用计数大于零),lock() 会返回一个新的 shared_ptr,使得我们可以安全地访问对象;如果对象已经被释放,lock() 会返回一个空的 shared_ptr。返回的 shared_ptr 需要被检查是否为空,以确保访问的安全性。

    weak_ptr 的典型应用场景:

    解决循环引用问题

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class ClassB; // 前向声明
    5
    6 class ClassA {
    7 public:
    8 std::shared_ptr<ClassB> bPtr;
    9 ~ClassA() { std::cout << "ClassA 析构" << std::endl; }
    10 };
    11
    12 class ClassB {
    13 public:
    14 std::weak_ptr<ClassA> aPtr; // 使用 weak_ptr 打破循环引用
    15 ~ClassB() { std::cout << "ClassB 析构" << std::endl; }
    16 };
    17
    18 int main() {
    19 std::shared_ptr<ClassA> a = std::make_shared<ClassA>();
    20 std::shared_ptr<ClassB> b = std::make_shared<ClassB>();
    21
    22 a->bPtr = b;
    23 b->aPtr = a; // 循环引用:a 指向 b,b 指向 a
    24
    25 // a 和 b 超出作用域,但由于循环引用,引用计数无法降为 0,导致内存泄漏
    26 return 0;
    27 } // 程序结束,但 ClassA 和 ClassB 的析构函数不会被调用,内存泄漏

    在上述代码中,ClassAClassB 通过 shared_ptr 相互引用,形成了循环引用。即使 ab 超出作用域,ClassAClassB 的析构函数也不会被调用,导致内存泄漏。将 ClassB 中的 std::shared_ptr<ClassA> aPtr; 改为 std::weak_ptr<ClassA> aPtr; 就可以打破循环引用。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class ClassB {
    2 public:
    3 std::weak_ptr<ClassA> aPtr; // 使用 weak_ptr 打破循环引用
    4 ~ClassB() { std::cout << "ClassB 析构" << std::endl; }
    5 };

    修改后的代码,当 ab 超出作用域时,ClassAClassB 的对象会被正确析构,解决了循环引用导致的内存泄漏问题。

    观察者模式 (Observer Pattern)
    在观察者模式中,观察者需要引用被观察者(主题),但不希望影响被观察者的生命周期。可以使用 weak_ptr 让观察者观察被观察者,而不会增加被观察者的引用计数。当被观察者被销毁时,观察者可以通过 weak_ptr 检测到这种情况。

    缓存 (Cache)
    在缓存系统中,缓存项可能需要引用原始对象,但不希望阻止原始对象的销毁。可以使用 weak_ptr 来引用原始对象。当原始对象被销毁时,缓存项可以通过 weak_ptr 检测到,并进行相应的处理(例如,从缓存中移除失效的缓存项)。

    总结: weak_ptr 是一种弱引用智能指针,不参与共享所有权,主要用于观察 shared_ptr 管理的对象,以及打破 shared_ptr 造成的循环引用。它通过 lock() 方法提供对所观察对象的安全访问,并在对象被销毁时返回空指针。weak_ptr 在解决循环引用、实现观察者模式和缓存等场景中非常有用,是现代 C++ 内存管理的重要组成部分。

    2. shared_ptr 基础 (shared_ptr Basics)

    2.1 shared_ptr 的基本概念 (Basic Concepts of shared_ptr)

    2.1.1 引用计数 (Reference Counting)

    引用计数 (Reference Counting) 是 shared_ptr 实现共享所有权 (shared ownership) 的核心机制。它追踪有多少个 shared_ptr 实例共同指向同一个对象。每当创建一个新的 shared_ptr 来指向某个对象,或者当 shared_ptr 通过拷贝构造或赋值操作共享对象的所有权时,该对象的引用计数就会原子性地 (atomically) 递增。当一个 shared_ptr 实例被销毁(例如,超出作用域)时,它所指向对象的引用计数就会原子性地 (atomically) 递减。

    当对象的引用计数降至零时,这意味着没有任何 shared_ptr 指向该对象,该对象会被安全地销毁,其占用的内存会被释放。这种自动管理对象生命周期的机制,极大地简化了 C++ 中的内存管理,并有效地避免了常见的内存泄漏 (memory leak) 和悬挂指针 (dangling pointer) 问题。

    示例代码:引用计数的变化

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 int main() {
    5 // ① 创建一个 shared_ptr,引用计数为 1
    6 std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    7 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    8
    9 // ② 拷贝构造 ptr2,引用计数递增为 2
    10 std::shared_ptr<int> ptr2 = ptr1;
    11 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    12 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 2
    13
    14 // ③ ptr1 超出作用域,引用计数递减为 1
    15 {
    16 std::shared_ptr<int> ptr3 = ptr1;
    17 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 3
    18 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 3
    19 std::cout << "ptr3 的引用计数: " << ptr3.use_count() << std::endl; // 输出 3
    20 } // ptr3 超出作用域,引用计数递减为 2
    21 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    22 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 2
    23
    24 // ④ ptr2 超出作用域,引用计数递减为 1
    25 ptr2 = nullptr; // 为了更清晰地展示 ptr2 的生命周期结束,显式地赋值 nullptr
    26 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    27
    28 // ⑤ ptr1 超出作用域,引用计数递减为 0,对象被销毁
    29 return 0;
    30 }

    在这个例子中,我们可以清晰地看到引用计数随着 shared_ptr 对象的创建、拷贝和销毁而变化。当最后一个 shared_ptr 实例 ptr1 超出作用域时,由于引用计数降为零,整数值 42 所占用的内存会被自动释放。

    2.1.2 共享所有权 (Shared Ownership)

    共享所有权 (shared ownership)shared_ptr 最重要的特性。它允许多个 shared_ptr 实例同时管理同一个动态分配的对象。这意味着,多个程序组件可以“共享”对某个对象的“所有权”,而无需担心何时释放该对象。只有当所有指向该对象的 shared_ptr 都被销毁或不再指向该对象时,被管理的对象才会被删除。

    共享所有权非常适用于以下场景:

    多个组件需要访问和管理同一资源: 例如,在图形界面程序中,多个窗口部件可能需要共享同一个数据模型。
    对象生命周期管理复杂: 当对象的生命周期不完全由单个组件控制,而是由多个组件的活动共同决定时,shared_ptr 可以简化生命周期管理。
    避免手动内存管理错误: 共享所有权机制自动处理对象的销毁,降低了因手动 delete 调用错误而导致的内存泄漏或提前释放的风险。

    示例代码:共享所有权的应用

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <vector>
    4
    5 class Resource {
    6 public:
    7 Resource(std::string name) : name_(name) {
    8 std::cout << "Resource '" << name_ << "' acquired." << std::endl;
    9 }
    10 ~Resource() {
    11 std::cout << "Resource '" << name_ << "' released." << std::endl;
    12 }
    13 std::string getName() const { return name_; }
    14 private:
    15 std::string name_;
    16 };
    17
    18 void useResource(std::shared_ptr<Resource> resPtr) {
    19 std::cout << "Using resource: " << resPtr->getName() << std::endl;
    20 // resPtr 在函数结束时超出作用域,但资源可能不会被释放,
    21 // 除非是最后一个指向该资源的 shared_ptr
    22 }
    23
    24 int main() {
    25 // ① 创建一个 shared_ptr 管理 Resource 对象
    26 std::shared_ptr<Resource> sharedResource = std::make_shared<Resource>("Shared Resource");
    27
    28 // ② 多个地方共享 Resource 的所有权
    29 useResource(sharedResource);
    30
    31 std::vector<std::shared_ptr<Resource>> resourceList;
    32 resourceList.push_back(sharedResource);
    33 resourceList.push_back(sharedResource);
    34
    35 std::cout << "sharedResource 引用计数: " << sharedResource.use_count() << std::endl; // 输出 3
    36
    37 // ③ 即使 sharedResource 变量超出作用域,Resource 对象仍然存在,
    38 // 因为 resourceList 中还持有 shared_ptr
    39 return 0;
    40 } // main 函数结束,resourceList 和 sharedResource 都超出作用域
    41 // 当 main 函数结束后,`resourceList` 和 `sharedResource` 都被销毁,
    42 // 最终当 `resourceList` 也被销毁后,最后一个 `shared_ptr` 实例消失,
    43 // `Resource` 对象的引用计数降为 0,资源才会被释放。

    在这个例子中,sharedResource 被多个地方共享:在 useResource 函数中以及在 resourceList 容器中。即使 main 函数中的 sharedResource 变量生命周期结束,Resource 对象仍然存活,直到 resourceList 也被销毁,表明共享所有权确保了资源在所有使用者都不再需要时才被释放。

    2.2 shared_ptr 的创建与初始化 (Creation and Initialization of shared_ptr)

    shared_ptr 提供了多种创建和初始化的方式,以适应不同的使用场景。正确地创建和初始化 shared_ptr 是确保资源管理安全和高效的关键步骤。

    2.2.1 使用 make_shared (Using make_shared)

    std::make_shared<T>(args...) 是创建 shared_ptr<T>推荐方法。它高效且异常安全 (exception-safe)make_shared 的主要优点在于,它会在一次内存分配中同时完成以下两件事:

    1. 为对象 T 分配内存。
    2. shared_ptr控制块 (control block) 分配内存。

    控制块 (control block) 包含了引用计数、弱引用计数以及可能的删除器 (deleter) 。将对象和控制块的内存分配合并为一次操作,不仅提高了效率,还增强了异常安全性。

    效率优势:

    减少内存分配次数: 传统的使用 new 创建对象再传递给 shared_ptr 构造函数的方式,需要两次独立的内存分配操作。make_shared 将两次分配合并为一次,减少了内存分配的开销,尤其是在频繁创建和销毁 shared_ptr 的场景下,性能提升更为明显。
    减少内存碎片: 一次性分配有助于减少内存碎片,提高内存利用率。

    异常安全优势:

    考虑以下使用 newshared_ptr 构造函数的代码:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 不推荐的 shared_ptr 创建方式 (Not Recommended)
    2 std::shared_ptr<MyClass> ptr(new MyClass(arg1, arg2));

    在执行 new MyClass(arg1, arg2)shared_ptr 构造函数之间,可能发生异常。如果 MyClass 的构造函数或 arg1, arg2 的计算过程中抛出异常,new MyClass(arg1, arg2) 已经分配的内存将无法被 shared_ptr 管理,从而导致内存泄漏。

    make_shared 的调用是原子性的,要么成功创建 shared_ptr 和对象,要么失败,不会产生中间状态,因此避免了这种潜在的内存泄漏风险,提供了更强的异常安全性。

    示例代码:使用 make_shared 创建 shared_ptr

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class MyClass {
    5 public:
    6 MyClass(int value) : value_(value) {
    7 std::cout << "MyClass 构造函数被调用, value = " << value_ << std::endl;
    8 }
    9 ~MyClass() {
    10 std::cout << "MyClass 析构函数被调用, value = " << value_ << std::endl;
    11 }
    12 int getValue() const { return value_; }
    13 private:
    14 int value_;
    15 };
    16
    17 int main() {
    18 // ① 使用 make_shared 创建 shared_ptr<MyClass>
    19 std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>(10);
    20 std::cout << "sharedPtr1 指向的值: " << sharedPtr1->getValue() << std::endl;
    21
    22 // ② 使用 auto 关键字,类型推导更简洁
    23 auto sharedPtr2 = std::make_shared<MyClass>(20);
    24 std::cout << "sharedPtr2 指向的值: " << sharedPtr2->getValue() << std::endl;
    25
    26 return 0;
    27 }

    在这个例子中,std::make_shared<MyClass>(10)std::make_shared<MyClass>(20) 分别创建了 shared_ptr<MyClass> 实例,并管理了新创建的 MyClass 对象。由于使用了 make_shared,内存分配更高效且安全。

    2.2.2 构造函数 (Constructors)

    shared_ptr 提供了多个构造函数,允许从不同来源创建 shared_ptr 实例。虽然 make_shared 是推荐的方式,但在某些情况下,我们可能需要使用构造函数来创建 shared_ptr

    常见的 shared_ptr 构造函数:

    默认构造函数 (Default Constructor): shared_ptr() noexcept;

    ⚝ 创建一个空的 shared_ptr,不指向任何对象。
    ⚝ 引用计数和指向的对象指针都为 nullptr
    ⚝ 适用于需要先声明 shared_ptr 变量,稍后再赋值的场景。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<int> ptr; // 默认构造,ptr 为空
    2 std::cout << "ptr 是否为空: " << (ptr ? "否" : "是") << std::endl; // 输出 "是"
    3 std::cout << "ptr 的引用计数: " << ptr.use_count() << std::endl; // 输出 0

    从裸指针构造 (Constructor from Raw Pointer): shared_ptr(T* ptr);

    ⚝ 接受一个裸指针 ptr,并创建一个 shared_ptr 来管理 ptr 指向的对象。
    重要: ptr 必须是通过 new 分配的内存,否则在 shared_ptr 析构时会尝试 deletenew 分配的内存,导致未定义行为 (undefined behavior)。
    避免: 除非有特殊理由,通常不推荐直接使用裸指针构造函数,优先使用 make_shared。如果必须使用裸指针,务必确保指针的有效性和所有权转移的正确性。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 int* rawPtr = new int(100);
    2 std::shared_ptr<int> ptr1(rawPtr); // 从裸指针构造 shared_ptr
    3 std::cout << "ptr1 指向的值: " << *ptr1 << std::endl;
    4 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    5
    6 // 容易出错的示例 (Error-prone example):
    7 // int value = 200;
    8 // std::shared_ptr<int> ptr2(&value); // 错误!ptr2 尝试管理栈上变量的地址,析构时会出错

    拷贝构造函数 (Copy Constructor): shared_ptr(const shared_ptr& other);

    ⚝ 创建一个新的 shared_ptr,与 other 共享所有权,指向同一个对象。
    ⚝ 引用计数会递增。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<int> ptr1 = std::make_shared<int>(50);
    2 std::shared_ptr<int> ptr2 = ptr1; // 拷贝构造
    3 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    4 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 2

    移动构造函数 (Move Constructor): shared_ptr(shared_ptr&& other) noexcept;

    ⚝ 将 other 的所有权转移给新的 shared_ptrother 变为空。
    不改变引用计数,只是所有权的转移。
    ⚝ 用于优化性能,避免不必要的引用计数增加和减少。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<int> createSharedPtr(int value) {
    2 return std::make_shared<int>(value); // 返回一个临时的 shared_ptr
    3 }
    4
    5 std::shared_ptr<int> ptr1 = createSharedPtr(60); // 移动构造
    6 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1

    unique_ptr 移动构造 (Constructor from unique_ptr): template<class Y> shared_ptr(unique_ptr<Y>&& other);

    ⚝ 从 unique_ptr 移动构造 shared_ptr,转移 unique_ptr 的独占所有权为共享所有权。
    unique_ptr other 变为空。
    ⚝ 实现了独占所有权到共享所有权的转换。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::unique_ptr<int> uniquePtr = std::make_unique<int>(70);
    2 std::shared_ptr<int> sharedPtr(std::move(uniquePtr)); // 从 unique_ptr 移动构造
    3 std::cout << "sharedPtr 指向的值: " << *sharedPtr << std::endl;
    4 std::cout << "uniquePtr 是否为空: " << (uniquePtr ? "否" : "是") << std::endl; // 输出 "是"

    带删除器 (deleter) 的构造函数 (Constructor with Deleter): template<class D> shared_ptr(T* ptr, D deleter);template<class D> shared_ptr(T* ptr, D deleter, std::allocator<U> alloc);

    ⚝ 允许指定自定义的删除器 (deleter),在引用计数降为零时,除了释放内存,还可以执行其他资源清理操作。
    deleter 可以是函数指针、函数对象或 Lambda 表达式。
    ⚝ 适用于管理非内存资源,例如文件句柄、网络连接等。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <fstream>
    2
    3 // 自定义删除器,用于关闭文件
    4 auto fileDeleter = [](std::ofstream* file) {
    5 std::cout << "执行自定义删除器,关闭文件" << std::endl;
    6 file->close();
    7 delete file;
    8 };
    9
    10 int main() {
    11 std::ofstream* filePtr = new std::ofstream("example.txt");
    12 if (filePtr->is_open()) {
    13 *filePtr << "Hello, Custom Deleter!" << std::endl;
    14 }
    15
    16 std::shared_ptr<std::ofstream> sharedFilePtr(filePtr, fileDeleter);
    17 // sharedFilePtr 管理 filePtr,并使用 fileDeleter 作为删除器
    18
    19 return 0;
    20 } // sharedFilePtr 超出作用域,fileDeleter 会被调用,关闭文件

    2.2.3 赋值操作 (Assignment Operations)

    shared_ptr 的赋值操作符允许将一个 shared_ptr 赋值给另一个 shared_ptr,从而实现所有权的转移或共享。赋值操作会影响引用计数和对象生命周期。

    拷贝赋值操作符 (Copy Assignment Operator): shared_ptr& operator=(const shared_ptr& other);

    ⚝ 将 other 赋值给当前的 shared_ptr 对象。
    ⚝ 如果当前的 shared_ptr 之前管理着一个对象,则该对象的引用计数会递减。如果递减后引用计数变为零,之前管理的对象会被销毁。
    ⚝ 新赋值的 shared_ptr 将与 other 共享所有权,指向同一个对象,引用计数会递增。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 int main() {
    5 std::shared_ptr<int> ptr1 = std::make_shared<int>(80);
    6 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    7
    8 std::shared_ptr<int> ptr2; // ptr2 默认构造,为空
    9 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 0
    10
    11 ptr2 = ptr1; // 拷贝赋值
    12 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    13 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 2
    14
    15 ptr1 = std::make_shared<int>(90); // ptr1 指向新对象
    16 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1 (指向新对象的 ptr1)
    17 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 1 (仍然指向旧对象的 ptr2)
    18 // 原 ptr1 指向的对象(值为 80 的 int)的引用计数从 2 变为 1,仍然被 ptr2 指向,所以不会被销毁。
    19
    20 return 0;
    21 }

    移动赋值操作符 (Move Assignment Operator): shared_ptr& operator=(shared_ptr&& other) noexcept;

    ⚝ 将 other 的所有权移动到当前的 shared_ptr 对象。
    ⚝ 如果当前的 shared_ptr 之前管理着一个对象,则该对象的引用计数会递减,可能导致对象销毁。
    other 变为空。
    不改变引用计数,仅转移所有权。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 std::shared_ptr<int> createSharedPtr(int value) {
    5 return std::make_shared<int>(value);
    6 }
    7
    8 int main() {
    9 std::shared_ptr<int> ptr1 = std::make_shared<int>(100);
    10 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    11
    12 std::shared_ptr<int> ptr2; // ptr2 默认构造,为空
    13 ptr2 = createSharedPtr(200); // 移动赋值,从临时 shared_ptr 赋值
    14 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 1 (ptr2 管理了新对象)
    15
    16 ptr1 = std::move(ptr2); // 移动赋值,ptr2 的所有权转移给 ptr1,ptr2 变为空
    17 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1 (ptr1 管理了原 ptr2 的对象)
    18 std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 输出 0 (ptr2 变为空)
    19
    20 return 0;
    21 }

    从裸指针赋值 (Assignment from Raw Pointer): 虽然 shared_ptr 没有直接接受裸指针的赋值操作符重载,但可以通过 reset() 方法实现类似的效果。reset(ptr) 的行为类似于先销毁当前 shared_ptr 管理的对象(如果存在),然后开始管理新的裸指针 ptr 指向的对象。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 int main() {
    5 std::shared_ptr<int> ptr1 = std::make_shared<int>(110);
    6 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    7
    8 int* rawPtr = new int(120);
    9 ptr1.reset(rawPtr); // ptr1 开始管理 rawPtr 指向的对象,之前对象(110)的引用计数降为 0 并被销毁
    10 std::cout << "ptr1 指向的值: " << *ptr1 << std::endl;
    11 std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    12
    13 ptr1.reset(); // 不带参数的 reset,ptr1 变为空,之前管理的对象(120)引用计数降为 0 并被销毁
    14 std::cout << "ptr1 是否为空: " << (ptr1 ? "否" : "是") << std::endl; // 输出 "是"
    15
    16 return 0;
    17 }

    2.3 shared_ptr 的使用 (Usage of shared_ptr)

    一旦创建了 shared_ptr,就可以像使用裸指针一样,通过解引用操作符和箭头操作符访问其管理的对象。shared_ptr 还提供了一些成员函数,用于获取原始指针等操作。

    2.3.1 解引用操作符 () (Dereference Operator ())

    解引用操作符 * 允许访问 shared_ptr 所指向的对象的值。如果 shared_ptr 为空 (null),则解引用操作会导致未定义行为,通常是程序崩溃。因此,在使用解引用操作符前,应该始终检查 shared_ptr 是否为空,或者确保在逻辑上 shared_ptr 不可能为空。

    示例代码:解引用操作符的使用

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 int main() {
    5 std::shared_ptr<int> ptr = std::make_shared<int>(130);
    6
    7 if (ptr) { // 检查 ptr 是否为空
    8 int value = *ptr; // 使用解引用操作符访问值
    9 std::cout << "ptr 指向的值: " << value << std::endl; // 输出 130
    10 *ptr = 140; // 修改 ptr 指向的值
    11 std::cout << "ptr 修改后的值: " << *ptr << std::endl; // 输出 140
    12 } else {
    13 std::cout << "ptr 为空,无法解引用" << std::endl;
    14 }
    15
    16 std::shared_ptr<int> emptyPtr; // 默认构造,为空
    17 if (emptyPtr) {
    18 // 这段代码不会执行,因为 emptyPtr 为空
    19 std::cout << *emptyPtr << std::endl; // 避免解引用空 shared_ptr
    20 } else {
    21 std::cout << "emptyPtr 为空" << std::endl; // 输出 "emptyPtr 为空"
    22 }
    23
    24 return 0;
    25 }

    2.3.2 箭头操作符 (->) (Arrow Operator (->))

    箭头操作符 -> 用于访问 shared_ptr 所指向对象的成员。它与裸指针的箭头操作符用法相同,提供了便捷的方式来调用对象的成员函数或访问成员变量。同样,如果 shared_ptr 为空,使用箭头操作符会导致未定义行为。

    示例代码:箭头操作符的使用

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <string>
    4
    5 class MyString {
    6 public:
    7 MyString(std::string str) : str_(str) {}
    8 void printString() const {
    9 std::cout << "字符串内容: " << str_ << std::endl;
    10 }
    11 std::string getSubstring(size_t pos, size_t len) const {
    12 return str_.substr(pos, len);
    13 }
    14 private:
    15 std::string str_;
    16 };
    17
    18 int main() {
    19 std::shared_ptr<MyString> strPtr = std::make_shared<MyString>("Hello, shared_ptr!");
    20
    21 if (strPtr) { // 检查 strPtr 是否为空
    22 strPtr->printString(); // 使用箭头操作符调用成员函数
    23 std::string sub = strPtr->getSubstring(7, 9); // 使用箭头操作符调用成员函数并返回值
    24 std::cout << "子字符串: " << sub << std::endl;
    25 } else {
    26 std::cout << "strPtr 为空,无法使用箭头操作符" << std::endl;
    27 }
    28
    29 std::shared_ptr<MyString> emptyStrPtr; // 默认构造,为空
    30 if (emptyStrPtr) {
    31 // 这段代码不会执行,因为 emptyStrPtr 为空
    32 emptyStrPtr->printString(); // 避免对空 shared_ptr 使用箭头操作符
    33 } else {
    34 std::cout << "emptyStrPtr 为空" << std::endl; // 输出 "emptyStrPtr 为空"
    35 }
    36
    37 return 0;
    38 }

    2.3.3 get() 方法 (get() Method)

    get() 方法是 shared_ptr 提供的一个成员函数,用于获取原始指针 (raw pointer)get() 方法返回 shared_ptr 内部存储的裸指针,但不增加引用计数,也不转移所有权。

    get() 方法的特点和注意事项:

    不转移所有权: 通过 get() 获取的裸指针,其生命周期仍然由 shared_ptr 管理。不要get() 返回的裸指针执行 delete 操作,这会导致双重释放 (double free) 错误,因为 shared_ptr 在析构时也会尝试释放其管理的对象。
    生命周期依赖: get() 返回的裸指针的有效性依赖于 shared_ptr 对象的生命周期。如果 shared_ptr 对象被销毁,裸指针将变为悬挂指针 (dangling pointer)
    使用场景: get() 方法通常用于与legacy code (遗留代码)需要裸指针接口的库进行交互。在现代 C++ 编程中,应尽量避免直接使用 get() 方法,优先使用 shared_ptr 提供的智能指针接口。

    示例代码:get() 方法的使用

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 void processRawPointer(int* rawPtr) {
    5 if (rawPtr) {
    6 std::cout << "处理裸指针,值为: " << *rawPtr << std::endl;
    7 // 注意:processRawPointer 函数不负责 rawPtr 的生命周期管理
    8 } else {
    9 std::cout << "裸指针为空" << std::endl;
    10 }
    11 }
    12
    13 int main() {
    14 std::shared_ptr<int> ptr = std::make_shared<int>(150);
    15 int* rawPtr = ptr.get(); // 获取裸指针,但不转移所有权
    16
    17 processRawPointer(rawPtr); // 将裸指针传递给需要裸指针的函数
    18
    19 std::cout << "ptr 的引用计数: " << ptr.use_count() << std::endl; // 输出 1,get() 不影响引用计数
    20
    21 // 错误示例:不要对 get() 返回的裸指针执行 delete 操作
    22 // delete rawPtr; // 错误!会导致双重释放
    23
    24 return 0;
    25 } // ptr 超出作用域,shared_ptr 负责释放内存,不会发生内存泄漏

    在这个例子中,get() 方法被用于获取 shared_ptr 内部的裸指针,并将其传递给一个接受裸指针的函数 processRawPointer。需要强调的是,processRawPointer 函数不应该管理裸指针的生命周期,资源的释放仍然由 shared_ptr ptr 负责。

    2.4 shared_ptr 的销毁与析构 (Destruction and Destructor of shared_ptr)

    shared_ptr 的自动内存管理机制的核心在于其析构函数。理解 shared_ptr 的销毁过程以及析构函数的调用时机,有助于更好地掌握其资源管理行为。

    2.4.1 析构函数的调用时机 (When Destructor is Called)

    shared_ptr 对象自身被销毁时(例如,超出作用域、被赋予新值、被 reset()),shared_ptr 的析构函数会被调用。shared_ptr 的析构函数的主要任务是:

    1. 递减引用计数: 将其所管理对象的引用计数原子性地 (atomically) 递减 1。
    2. 检查引用计数: 检查递减后的引用计数是否为零。
    3. 销毁对象 (如果引用计数为零): 如果引用计数变为零,表示没有其他 shared_ptr 实例指向该对象,此时 shared_ptr 会负责销毁所管理的对象,即调用对象的析构函数,并释放对象占用的内存。如果指定了自定义删除器 (custom deleter),则会调用删除器来释放资源,而不是简单的 delete 操作。

    示例代码:析构函数的调用时机

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class MyObject {
    5 public:
    6 MyObject(int id) : id_(id) {
    7 std::cout << "MyObject " << id_ << " 构造函数被调用" << std::endl;
    8 }
    9 ~MyObject() {
    10 std::cout << "MyObject " << id_ << " 析构函数被调用" << std::endl;
    11 }
    12 private:
    13 int id_;
    14 };
    15
    16 int main() {
    17 {
    18 std::shared_ptr<MyObject> ptr1 = std::make_shared<MyObject>(1);
    19 std::cout << "ptr1 创建,引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    20
    21 {
    22 std::shared_ptr<MyObject> ptr2 = ptr1; // 拷贝构造
    23 std::cout << "ptr2 创建,引用计数: " << ptr1.use_count() << std::endl; // 输出 2
    24 } // ptr2 超出作用域,ptr2 的析构函数被调用,引用计数递减
    25 std::cout << "ptr2 销毁,引用计数: " << ptr1.use_count() << std::endl; // 输出 1
    26 } // ptr1 超出作用域,ptr1 的析构函数被调用,引用计数递减为 0,MyObject 对象被销毁
    27 std::cout << "ptr1 销毁,MyObject 对象应该已被销毁" << std::endl;
    28
    29 return 0;
    30 }

    在这个例子中,MyObject 的析构函数只在 ptr1 也超出作用域之后才被调用,因为只有当最后一个指向 MyObject 对象的 shared_ptr(即 ptr1)被销毁时,引用计数才降为零,对象才会被释放。

    2.4.2 资源释放 (Resource Release)

    shared_ptr 销毁其管理的对象时,实际的资源释放过程取决于 shared_ptr 的创建方式和是否使用了自定义删除器。

    默认删除器 (Default Deleter): 如果使用 make_shared 或从裸指针构造 shared_ptr 且没有指定自定义删除器,shared_ptr 默认使用 delete 运算符来释放内存。对于数组类型 (e.g., shared_ptr<int[]>),默认删除器是 delete[]

    自定义删除器 (Custom Deleter): 如果在创建 shared_ptr 时指定了自定义删除器,当引用计数降为零时,shared_ptr 会调用用户提供的删除器来释放资源。自定义删除器可以是函数指针、函数对象或 Lambda 表达式,允许执行更复杂的资源清理操作,例如关闭文件、释放系统资源、归还数据库连接等。

    示例代码:资源释放与自定义删除器

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 // 自定义删除器,模拟释放文件句柄
    5 auto fileDeleter = [](int* fd) {
    6 std::cout << "自定义删除器被调用,释放文件句柄 " << *fd << std::endl;
    7 // 实际场景中,这里会执行 close(*fd) 等操作
    8 delete fd;
    9 };
    10
    11 int main() {
    12 int* fileDescriptor = new int(101); // 模拟文件句柄
    13 std::cout << "文件句柄 " << *fileDescriptor << " 已分配" << std::endl;
    14
    15 {
    16 std::shared_ptr<int> filePtr(fileDescriptor, fileDeleter); // 使用自定义删除器
    17 std::cout << "filePtr 创建,引用计数: " << filePtr.use_count() << std::endl; // 输出 1
    18 } // filePtr 超出作用域,析构函数被调用,引用计数递减为 0,自定义删除器 fileDeleter 被调用
    19
    20 std::cout << "filePtr 销毁,自定义删除器应该已被调用" << std::endl;
    21
    22 return 0;
    23 }

    在这个例子中,fileDeleter Lambda 表达式作为自定义删除器,在 shared_ptr filePtr 析构时被调用,模拟了文件句柄的释放操作。这展示了 shared_ptr 如何通过自定义删除器来管理各种类型的资源,而不仅仅是内存。

    通过本章的学习,我们掌握了 shared_ptr 的基本概念、创建、使用和销毁过程。理解引用计数、共享所有权以及析构函数的行为,是深入理解和正确使用 shared_ptr 的基础。在接下来的章节中,我们将进一步探讨 shared_ptr 的高级特性和应用场景。

    3. shared_ptr 深度解析 (In-depth Analysis of shared_ptr)

    本章深入分析了 shared_ptr 的内部机制,包括引用计数实现、控制块 (control block)、线程安全 (thread safety) 和自定义删除器 (custom deleters)。通过本章的学习,读者将能够透彻理解 shared_ptr 的底层工作原理,为更高级的应用和问题排查打下坚实的基础。 (This chapter provides a deeper analysis of shared_ptr's internal mechanisms, including reference counting implementation, control blocks, thread safety, and custom deleters. By studying this chapter, readers will gain a thorough understanding of the underlying working principles of shared_ptr, laying a solid foundation for more advanced applications and troubleshooting.)

    3.1 引用计数的原理与实现 (Principle and Implementation of Reference Counting)

    本节深入探讨引用计数 (reference counting) 的技术细节,包括原子操作 (atomic operations) 及其在 shared_ptr 中的具体实现方式。引用计数是 shared_ptr 实现共享所有权和自动内存管理的核心机制,理解其原理对于掌握 shared_ptr 至关重要。(This section delves into the technical details of reference counting, including atomic operations and its specific implementation in shared_ptr. Reference counting is the core mechanism for shared_ptr to achieve shared ownership and automatic memory management, and understanding its principles is crucial for mastering shared_ptr.)

    3.1.1 原子操作 (Atomic Operations)

    在多线程 (multi-threaded) 环境中,多个线程可能同时访问和修改同一个 shared_ptr 实例,这涉及到对引用计数的并发操作。为了保证引用计数的正确性和线程安全 (thread-safe),shared_ptr 的实现依赖于原子操作 (atomic operations)。原子操作是指不可被中断的一个或一组操作。一旦原子操作开始执行,它必须不被打断地执行完成。在 C++11 标准中,<atomic> 头文件提供了原子类型和原子操作的支持。(In a multi-threaded environment, multiple threads may access and modify the same shared_ptr instance simultaneously, which involves concurrent operations on the reference count. To ensure the correctness and thread safety of the reference count, the implementation of shared_ptr relies on atomic operations. Atomic operations refer to one or a group of operations that cannot be interrupted. Once an atomic operation starts, it must be executed to completion without interruption. In the C++11 standard, the <atomic> header file provides support for atomic types and atomic operations.)

    原子性 (Atomicity):原子操作保证了操作的原子性,即一个操作要么完全完成,要么完全不执行,不会出现执行到一半被其他线程打断的情况。这对于引用计数的递增和递减操作至关重要,防止出现数据竞争 (data race) 和不一致的状态。(Atomicity: Atomic operations ensure the atomicity of operations, meaning an operation either completes entirely or not at all, and will not be interrupted by other threads midway. This is crucial for the increment and decrement operations of the reference count, preventing data races and inconsistent states.)

    内存顺序 (Memory Ordering):原子操作还涉及到内存顺序 (memory ordering) 的概念,它定义了原子操作对内存的影响,以及不同线程之间内存操作的可见性。C++11 提供了多种内存顺序选项,例如 std::memory_order_relaxed, std::memory_order_acquire, std::memory_order_release, std::memory_order_acq_rel, 和 std::memory_order_seq_cstshared_ptr 的引用计数操作通常使用 std::memory_order_acq_relstd::memory_order_seq_cst 来保证在多线程环境下的正确同步。(Memory Ordering: Atomic operations also involve the concept of memory ordering, which defines the impact of atomic operations on memory and the visibility of memory operations between different threads. C++11 provides various memory ordering options, such as std::memory_order_relaxed, std::memory_order_acquire, std::memory_order_release, std::memory_order_acq_rel, and std::memory_order_seq_cst. Reference count operations in shared_ptr typically use std::memory_order_acq_rel or std::memory_order_seq_cst to ensure correct synchronization in multi-threaded environments.)

    例如,当创建一个新的 shared_ptr 或复制一个 shared_ptr 时,引用计数需要原子地递增;当 shared_ptr 析构时,引用计数需要原子地递减。以下代码片段示意性地展示了原子操作在引用计数中的应用(注意:这只是概念性代码,并非 shared_ptr 的实际实现):(For example, when a new shared_ptr is created or a shared_ptr is copied, the reference count needs to be atomically incremented; when a shared_ptr is destructed, the reference count needs to be atomically decremented. The following code snippet schematically demonstrates the application of atomic operations in reference counting (Note: This is conceptual code and not the actual implementation of shared_ptr):)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <atomic>
    2
    3 class RefCount {
    4 public:
    5 RefCount() : count_(0) {}
    6
    7 void increment() {
    8 count_.fetch_add(1, std::memory_order_acq_rel); // 原子递增 (Atomic increment)
    9 }
    10
    11 void decrement() {
    12 if (count_.fetch_sub(1, std::memory_order_acq_rel) == 1) { // 原子递减并检查是否为 0 (Atomic decrement and check if it becomes 0)
    13 // 引用计数变为 0,执行资源释放 (Reference count becomes 0, perform resource release)
    14 delete this; // 假设 RefCount 对象管理着某些资源 (Assume RefCount object manages some resources)
    15 }
    16 }
    17
    18 int getCount() const {
    19 return count_.load(std::memory_order_relaxed); // 原子读取 (Atomic read)
    20 }
    21
    22 private:
    23 std::atomic<int> count_; // 原子计数器 (Atomic counter)
    24 };

    在这个简化的例子中,std::atomic<int> 用于存储引用计数,fetch_addfetch_sub 是原子递增和递减操作,load 是原子读取操作。通过使用原子操作,可以确保在多线程环境下对引用计数的修改是安全的。(In this simplified example, std::atomic<int> is used to store the reference count, fetch_add and fetch_sub are atomic increment and decrement operations, and load is an atomic read operation. By using atomic operations, it is ensured that modifications to the reference count are safe in a multi-threaded environment.)

    3.1.2 引用计数的内部实现 (Internal Implementation of Reference Counting)

    shared_ptr 的引用计数并非直接存储在 shared_ptr 对象本身中,而是存储在控制块 (control block) 中。控制块是动态分配的一块内存,用于管理与 shared_ptr 关联的元数据,包括:(The reference count of shared_ptr is not directly stored in the shared_ptr object itself, but in the control block. The control block is a dynamically allocated block of memory used to manage the metadata associated with the shared_ptr, including:)

    强引用计数 (Strong Reference Count):也称为共享计数 (shared count),记录了当前有多少个 shared_ptr 实例共享同一个对象的所有权。当强引用计数降为 0 时,表示没有任何 shared_ptr 指向该对象,被管理的对象会被析构和释放。(Strong Reference Count: Also known as shared count, it records how many shared_ptr instances currently share ownership of the same object. When the strong reference count drops to 0, it indicates that no shared_ptr points to the object, and the managed object will be destructed and released.)

    弱引用计数 (Weak Reference Count):记录了当前有多少个 weak_ptr 实例指向同一个对象。弱引用计数的存在是为了在强引用计数为 0 后,仍然能够安全地释放控制块的内存。即使强引用计数为 0,只要弱引用计数不为 0,控制块就不能被释放,因为 weak_ptr 可能还需要访问控制块来检查对象是否仍然存活。(Weak Reference Count: It records how many weak_ptr instances currently point to the same object. The existence of the weak reference count is to ensure that the memory of the control block can be safely released even after the strong reference count becomes 0. Even if the strong reference count is 0, as long as the weak reference count is not 0, the control block cannot be released, because weak_ptr may still need to access the control block to check if the object is still alive.)

    删除器 (Deleter):用于在强引用计数为 0 时,释放被管理对象的资源。删除器可以是默认的 delete 操作符,也可以是用户自定义的函数对象或 Lambda 表达式。(Deleter: Used to release the resources of the managed object when the strong reference count becomes 0. The deleter can be the default delete operator, or a user-defined function object or Lambda expression.)

    分配器 (Allocator) (可选):用于分配和释放控制块内存的分配器。通常情况下,shared_ptr 使用默认的 std::allocator。(Allocator (optional): An allocator used to allocate and deallocate control block memory. In most cases, shared_ptr uses the default std::allocator.)

    当使用 make_shared<T>(...) 创建 shared_ptr 时,控制块和被管理的对象会一起分配在同一块连续的内存中,这被称为控制块与对象合并分配。这种方式可以提高内存分配效率,并减少内存碎片。而当使用 shared_ptr<T>(new T(...)) 构造函数创建 shared_ptr 时,控制块和被管理的对象通常会分别分配在不同的内存块中。(When using make_shared<T>(...) to create a shared_ptr, the control block and the managed object are allocated together in the same contiguous block of memory, which is called control block and object co-allocation. This approach can improve memory allocation efficiency and reduce memory fragmentation. When using the shared_ptr<T>(new T(...)) constructor to create a shared_ptr, the control block and the managed object are usually allocated in different memory blocks separately.)

    下图示意性地展示了控制块的结构以及 shared_ptr 与控制块和被管理对象之间的关系:(The following diagram schematically illustrates the structure of the control block and the relationship between shared_ptr, the control block, and the managed object:)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 +-----------------+ +---------------+ +-----------------+
    2 | shared_ptr | --> | Control Block | --> | Managed Object |
    3 +-----------------+ +---------------+ +-----------------+
    4 | Pointer to | | Strong Ref Count| | Data of Type T |
    5 | Control Block | | Weak Ref Count | +-----------------+
    6 | (and Managed | | Deleter |
    7 | Object - if | | Allocator |
    8 | make_shared used)| +---------------+
    9 +-----------------+

    理解控制块的存在和作用,有助于深入理解 shared_ptr 的内存管理和生命周期管理机制。(Understanding the existence and role of the control block helps to deeply understand the memory management and lifecycle management mechanisms of shared_ptr.)

    3.2 控制块 (Control Block)

    控制块 (control block) 是 shared_ptr 实现共享所有权和资源管理的关键组成部分。它存储了引用计数、删除器等元数据,并负责在适当的时候析构被管理的对象和释放相关资源。本节将详细介绍控制块的结构和生命周期。(The control block is a crucial component of shared_ptr for implementing shared ownership and resource management. It stores metadata such as reference counts and deleters, and is responsible for destructing the managed object and releasing related resources at the appropriate time. This section will detail the structure and lifecycle of the control block.)

    3.2.1 控制块的结构 (Structure of Control Block)

    控制块 (control block) 通常包含以下几个关键组成部分:(The control block typically contains the following key components:)

    强引用计数 (Strong Reference Count):一个原子整数,记录了当前有多少个 shared_ptr 实例指向被管理对象。对强引用计数的修改必须是原子操作,以保证线程安全。当最后一个指向对象的 shared_ptr 被销毁或重置时,强引用计数会递减。当强引用计数从 1 变为 0 时,表示没有任何 shared_ptr 拥有该对象,此时会触发删除器,释放被管理对象的资源。(Strong Reference Count: An atomic integer that records how many shared_ptr instances currently point to the managed object. Modifications to the strong reference count must be atomic operations to ensure thread safety. When the last shared_ptr pointing to the object is destroyed or reset, the strong reference count decrements. When the strong reference count changes from 1 to 0, it indicates that no shared_ptr owns the object, at which point the deleter is triggered to release the resources of the managed object.)

    弱引用计数 (Weak Reference Count):一个原子整数,记录了当前有多少个 weak_ptr 实例指向被管理对象(或更准确地说,指向控制块)。弱引用计数的存在是为了确保在强引用计数为 0 后,控制块不会立即被释放,直到所有指向该控制块的 weak_ptr 都被销毁。只有当强引用计数和弱引用计数都变为 0 时,控制块的内存才会被释放。(Weak Reference Count: An atomic integer that records how many weak_ptr instances currently point to the managed object (or more accurately, to the control block). The existence of the weak reference count ensures that the control block is not immediately released after the strong reference count becomes 0, until all weak_ptr instances pointing to this control block are destroyed. Only when both the strong reference count and the weak reference count become 0, the memory of the control block is released.)

    删除器 (Deleter):一个函数对象、函数指针或 Lambda 表达式,负责释放被管理对象的资源。删除器在强引用计数变为 0 时被调用。对于使用 make_sharedshared_ptr 构造函数并传入自定义删除器的情况,控制块会存储这个自定义删除器。如果没有提供自定义删除器,则默认使用 delete 操作符。(Deleter: A function object, function pointer, or Lambda expression responsible for releasing the resources of the managed object. The deleter is called when the strong reference count becomes 0. For cases where a custom deleter is passed when using make_shared or the shared_ptr constructor, the control block will store this custom deleter. If no custom deleter is provided, the delete operator is used by default.)

    被管理对象的指针 (Managed Object Pointer):存储指向被 shared_ptr 管理的对象的指针。对于空 shared_ptr,这个指针可能为空。(Managed Object Pointer: Stores a pointer to the object managed by the shared_ptr. For empty shared_ptr instances, this pointer may be null.)

    分配器 (Allocator) (可选):用于分配控制块内存的分配器。如果使用了自定义分配器,控制块可能需要存储分配器的信息。(Allocator (optional): An allocator used to allocate control block memory. If a custom allocator is used, the control block may need to store allocator information.)

    控制块的结构和具体实现可能因编译器和标准库版本而略有不同,但核心组成部分和功能基本一致。理解控制块的结构有助于深入理解 shared_ptr 的内存管理机制。(The structure and specific implementation of the control block may vary slightly depending on the compiler and standard library version, but the core components and functions are basically the same. Understanding the structure of the control block helps to deeply understand the memory management mechanism of shared_ptr.)

    3.2.2 控制块的生命周期 (Lifecycle of Control Block)

    控制块 (control block) 的生命周期与 shared_ptrweak_ptr 的生命周期紧密相关。控制块的创建和销毁时机是理解 shared_ptr 内存管理的关键。(The lifecycle of the control block is closely related to the lifecycles of shared_ptr and weak_ptr. The timing of the creation and destruction of the control block is key to understanding shared_ptr memory management.)

    控制块的创建 (Creation of Control Block):控制块在以下情况下被创建:(Creation of Control Block: The control block is created in the following situations:)
    ▮▮▮▮ⓑ 使用 make_shared<T>(...): make_shared 是创建 shared_ptr 的推荐方式。它会一次性分配足够的内存来存储控制块和被管理对象,并将控制块放置在这块内存的前部或后部,然后构造对象。控制块的强引用计数和弱引用计数初始值都为 1。(Using make_shared<T>(...): make_shared is the recommended way to create shared_ptr. It allocates memory in one go sufficient to store the control block and the managed object, places the control block at the front or back of this memory block, and then constructs the object. The initial values of both the strong and weak reference counts in the control block are 1.)

    ▮▮▮▮ⓑ 使用 shared_ptr<T>(new T(...)): 当使用 new 表达式创建对象并将其传递给 shared_ptr 构造函数时,控制块会被单独分配。控制块的强引用计数初始值为 1,弱引用计数初始值为 0。(Using shared_ptr<T>(new T(...)): When an object is created using the new expression and passed to the shared_ptr constructor, the control block is allocated separately. The initial strong reference count of the control block is 1, and the initial weak reference count is 0.)

    ▮▮▮▮ⓒ unique_ptrweak_ptr 转换而来: 当从 unique_ptr 移动构造或赋值给 shared_ptr,或者从 weak_ptr 调用 lock() 成功时,如果目标 shared_ptr 之前为空,则会创建新的控制块(或增加已有控制块的引用计数)。(Conversion from unique_ptr or weak_ptr: When move constructing or assigning from a unique_ptr to a shared_ptr, or when lock() is successfully called from a weak_ptr, if the target shared_ptr was previously empty, a new control block will be created (or the reference count of an existing control block will be increased).)

    控制块的销毁 (Destruction of Control Block):控制块在以下情况下被销毁:(Destruction of Control Block: The control block is destroyed in the following situations:)

    ▮▮▮▮ⓐ 强引用计数变为 0: 当最后一个指向对象的 shared_ptr 被销毁或重置,导致强引用计数从 1 变为 0 时,会调用控制块中存储的删除器来释放被管理对象的资源。但是,此时控制块本身可能不会立即被释放,因为可能还有 weak_ptr 指向该控制块,弱引用计数可能仍然大于 0。(Strong Reference Count Becomes 0: When the last shared_ptr pointing to the object is destroyed or reset, causing the strong reference count to change from 1 to 0, the deleter stored in the control block is called to release the resources of the managed object. However, at this point, the control block itself may not be immediately released, because there may still be weak_ptr instances pointing to this control block, and the weak reference count may still be greater than 0.)

    ▮▮▮▮ⓑ 弱引用计数也变为 0: 只有当强引用计数和弱引用计数都变为 0 时,控制块的内存才会被真正释放。这意味着,即使对象已经被析构,只要还有 weak_ptr 指向它,控制块就仍然存在,直到所有 weak_ptr 也被销毁。这保证了 weak_ptr 可以安全地检查所观测的对象是否仍然存活,即使对象已经被 shared_ptr 管理的所有者释放。(Weak Reference Count Also Becomes 0: Only when both the strong reference count and the weak reference count become 0, the memory of the control block is truly released. This means that even if the object has been destructed, as long as there are still weak_ptr instances pointing to it, the control block will still exist until all weak_ptr instances are also destroyed. This ensures that weak_ptr can safely check whether the observed object is still alive, even if the object has been released by all shared_ptr owners.)

    理解控制块的生命周期对于避免内存泄漏 (memory leak) 和悬挂指针 (dangling pointer) 问题至关重要。特别是在处理循环引用 (circular reference) 的场景中,weak_ptr 的作用以及控制块的生命周期管理显得尤为重要。(Understanding the lifecycle of the control block is crucial for avoiding memory leaks and dangling pointer issues. Especially when dealing with circular reference scenarios, the role of weak_ptr and the lifecycle management of the control block are particularly important.)

    3.3 线程安全性 (Thread Safety)

    线程安全性 (thread safety) 是 shared_ptr 设计中需要重点考虑的方面。在多线程 (multi-threaded) 环境下,多个线程可能会并发地访问和操作 shared_ptr 实例。shared_ptr 本身在设计上是线程安全的,但其线程安全级别有一定的限制。本节将详细讨论 shared_ptr 的线程安全性。(Thread safety is an important aspect to consider in the design of shared_ptr. In a multi-threaded environment, multiple threads may concurrently access and operate on shared_ptr instances. shared_ptr itself is designed to be thread-safe, but its thread safety level has certain limitations. This section will discuss the thread safety of shared_ptr in detail.)

    3.3.1 多线程环境下的 shared_ptr (shared_ptr in Multi-threaded Environments)

    shared_ptr引用计数操作是原子性的,这意味着在多线程环境下,对同一个控制块的引用计数进行递增和递减操作是线程安全的。多个线程可以同时安全地拷贝、赋值和析构 shared_ptr 对象,而不会导致数据竞争 (data race) 或引用计数错误。(The reference count operations of shared_ptr are atomic, which means that in a multi-threaded environment, incrementing and decrementing the reference count of the same control block are thread-safe. Multiple threads can safely copy, assign, and destruct shared_ptr objects simultaneously without causing data races or reference count errors.)

    线程安全保证的范围 (Scope of Thread Safety Guarantee)

    多个线程同时操作 不同的 shared_ptr 实例,即使这些 shared_ptr 实例 指向同一个对象,是线程安全的。因为每个 shared_ptr 实例都拥有对控制块的原子操作能力。(Operating on different shared_ptr instances simultaneously by multiple threads, even if these shared_ptr instances point to the same object, is thread-safe. Because each shared_ptr instance has atomic operation capabilities on the control block.)

    多个线程同时通过 同一个 shared_ptr 实例读取被管理的对象 (通过 get()*-> 操作符),是线程安全的,只要被管理对象自身的操作也是线程安全的shared_ptr 只负责管理对象的生命周期,不负责对象内部状态的线程安全。(Reading the managed object through the same shared_ptr instance (using get(), *, -> operators) by multiple threads is thread-safe, as long as the operations on the managed object itself are also thread-safe. shared_ptr is only responsible for managing the lifecycle of the object, not for the thread safety of the object's internal state.)

    多个线程同时通过 同一个 shared_ptr 实例 修改 被管理的对象 (例如,通过 *ptr = ...ptr->method(...)),如果被管理对象自身的操作 不是 线程安全的,那么这种操作就 不是 线程安全的shared_ptr 不提供对被管理对象内部状态的任何线程安全保护。(Modifying the managed object through the same shared_ptr instance (e.g., using *ptr = ... or ptr->method(...)) by multiple threads, if the operations on the managed object itself are not thread-safe, then this operation is not thread-safe. shared_ptr does not provide any thread safety protection for the internal state of the managed object.)

    线程不安全的情况 (Thread-unsafe Scenarios)

    修改 同一个 shared_ptr 实例 (例如,赋值操作 ptr1 = ptr2ptr1.reset()) 不是原子操作,因此在多线程环境下对 同一个 shared_ptr 实例进行修改操作 不是 线程安全的。需要使用互斥锁 (mutex) 等同步机制来保护对 shared_ptr 实例本身的并发修改。(Modifying the same shared_ptr instance (e.g., assignment operation ptr1 = ptr2, ptr1.reset()) is not an atomic operation, so modifying operations on the same shared_ptr instance in a multi-threaded environment are not thread-safe. Synchronization mechanisms such as mutexes need to be used to protect concurrent modifications to the shared_ptr instance itself.)

    例如,以下代码展示了错误的多线程 shared_ptr 使用方式:(For example, the following code demonstrates incorrect multi-threaded shared_ptr usage:)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <thread>
    4 #include <vector>
    5
    6 std::shared_ptr<int> global_ptr; // 全局 shared_ptr (Global shared_ptr)
    7
    8 void thread_func() {
    9 for (int i = 0; i < 1000; ++i) {
    10 global_ptr = std::make_shared<int>(i); // 线程不安全的操作 (Thread-unsafe operation)
    11 // ... 对 global_ptr 指向的对象进行操作 (Operations on the object pointed to by global_ptr)
    12 }
    13 }
    14
    15 int main() {
    16 std::vector<std::thread> threads;
    17 for (int i = 0; i < 10; ++i) {
    18 threads.emplace_back(thread_func);
    19 }
    20
    21 for (auto& thread : threads) {
    22 thread.join();
    23 }
    24 return 0;
    25 }

    在这个例子中,多个线程同时修改全局 shared_ptr 变量 global_ptr,这 不是 线程安全的。正确的做法是使用互斥锁保护对 global_ptr 的修改,或者避免多个线程同时修改同一个 shared_ptr 实例。(In this example, multiple threads are simultaneously modifying the global shared_ptr variable global_ptr, which is not thread-safe. The correct approach is to use a mutex to protect modifications to global_ptr, or to avoid multiple threads modifying the same shared_ptr instance simultaneously.)

    3.3.2 线程安全的操作 (Thread-safe Operations)

    以下操作在 shared_ptr 上是线程安全的:(The following operations on shared_ptr are thread-safe:)

    拷贝构造 (Copy Construction):从一个 shared_ptr 拷贝构造另一个 shared_ptr 是线程安全的。(Copy Construction: Copy constructing a shared_ptr from another shared_ptr is thread-safe.)

    拷贝赋值 (Copy Assignment):将一个 shared_ptr 赋值给另一个 shared_ptr 是线程安全的 (但注意是对 不同的 shared_ptr 实例进行赋值)。(Copy Assignment: Assigning one shared_ptr to another shared_ptr is thread-safe (but note that this is assignment to different shared_ptr instances).)

    析构 (Destruction)shared_ptr 对象的析构是线程安全的。(Destruction: Destruction of a shared_ptr object is thread-safe.)

    读取被管理的对象 (Reading Managed Object):通过 get()*-> 等操作符读取被管理对象是线程安全的,前提是被管理对象自身的读取操作也是线程安全的。(Reading Managed Object: Reading the managed object through operators like get(), *, -> is thread-safe, provided that the read operations on the managed object itself are also thread-safe.)

    use_count(): 获取当前引用计数值是线程安全的,但需要注意,即使在获取到引用计数值后,该值可能已经发生变化,因此 use_count() 的返回值不适合作为线程同步的依据,主要用于调试和诊断。(use_count(): Getting the current reference count value is thread-safe, but it should be noted that even after obtaining the reference count value, the value may have already changed. Therefore, the return value of use_count() is not suitable as a basis for thread synchronization, and is mainly used for debugging and diagnostics.)

    总结 (Summary)

    shared_ptr 提供了引用计数操作的线程安全,保证了多个线程可以安全地共享和管理对象的生命周期。但是,对于 同一个 shared_ptr 实例的修改操作 (如赋值、reset) 以及被管理对象自身的线程安全,shared_ptr 不提供任何保证。在多线程编程中使用 shared_ptr 时,需要仔细考虑线程安全问题,并根据具体情况采取适当的同步措施。(shared_ptr provides thread safety for reference count operations, ensuring that multiple threads can safely share and manage the lifecycle of objects. However, for modification operations on the same shared_ptr instance (such as assignment, reset) and the thread safety of the managed object itself, shared_ptr does not provide any guarantees. When using shared_ptr in multi-threaded programming, thread safety issues need to be carefully considered, and appropriate synchronization measures should be taken according to specific situations.)

    3.4 自定义删除器 (Custom Deleters)

    shared_ptr 允许用户指定自定义删除器 (custom deleter),用于在强引用计数变为 0 时,执行特定的资源释放操作,而不仅仅是简单的 delete 操作符。自定义删除器为 shared_ptr 提供了更灵活的资源管理能力,可以用于管理各种类型的资源,例如文件句柄、网络连接、互斥锁等。(shared_ptr allows users to specify a custom deleter, which is used to perform specific resource release operations when the strong reference count becomes 0, rather than just the simple delete operator. Custom deleters provide shared_ptr with more flexible resource management capabilities, and can be used to manage various types of resources, such as file handles, network connections, mutexes, etc.)

    3.4.1 函数对象作为删除器 (Function Objects as Deleters)

    可以使用函数对象 (function object) (也称为仿函数 (functor)) 作为 shared_ptr 的自定义删除器。函数对象是一个重载了函数调用操作符 operator() 的类。当 shared_ptr 管理的对象需要被删除时,shared_ptr 会调用这个函数对象的 operator() 方法来执行自定义的删除操作。(A function object (also known as a functor) can be used as a custom deleter for shared_ptr. A function object is a class that overloads the function call operator operator(). When the object managed by shared_ptr needs to be deleted, shared_ptr will call the operator() method of this function object to perform the custom deletion operation.)

    例如,假设需要使用 shared_ptr 管理通过 fopen 打开的文件句柄,而不是通过 new 分配的内存。此时,可以使用一个函数对象作为删除器,在删除器中调用 fclose 关闭文件句柄:(For example, suppose you need to use shared_ptr to manage file handles opened by fopen, rather than memory allocated by new. In this case, you can use a function object as a deleter, and call fclose in the deleter to close the file handle:)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <cstdio>
    4
    5 // 文件句柄关闭器 (File handle closer)
    6 struct FileCloser {
    7 void operator()(FILE* fp) {
    8 if (fp) {
    9 std::cout << "Closing file handle." << std::endl;
    10 fclose(fp);
    11 }
    12 }
    13 };
    14
    15 int main() {
    16 FILE* fp = fopen("example.txt", "r");
    17 if (!fp) {
    18 std::cerr << "Failed to open file." << std::endl;
    19 return 1;
    20 }
    21
    22 std::shared_ptr<FILE> file_ptr(fp, FileCloser()); // 使用 FileCloser 作为删除器 (Use FileCloser as deleter)
    23
    24 // ... 使用 file_ptr 操作文件 (Use file_ptr to operate on the file)
    25 char buffer[100];
    26 if (fgets(buffer, sizeof(buffer), file_ptr.get())) {
    27 std::cout << "Read from file: " << buffer << std::endl;
    28 }
    29
    30 return 0;
    31 } // file_ptr 析构,FileCloser::operator() 被调用,文件句柄被关闭 (file_ptr is destructed, FileCloser::operator() is called, file handle is closed)

    在这个例子中,FileCloser 是一个函数对象,其 operator() 接受一个 FILE* 指针,并在其中调用 fclose 关闭文件句柄。在创建 shared_ptr file_ptr 时,将 fpFileCloser() 实例作为参数传递给 shared_ptr 的构造函数,指定了使用 FileCloser 作为删除器。当 file_ptr 的强引用计数变为 0 时,FileCloser::operator() 会被自动调用,确保文件句柄被正确关闭。(In this example, FileCloser is a function object whose operator() accepts a FILE* pointer and calls fclose in it to close the file handle. When creating the shared_ptr file_ptr, fp and a FileCloser() instance are passed as parameters to the shared_ptr constructor, specifying that FileCloser should be used as the deleter. When the strong reference count of file_ptr becomes 0, FileCloser::operator() will be automatically called, ensuring that the file handle is closed correctly.)

    3.4.2 Lambda 表达式作为删除器 (Lambda Expressions as Deleters)

    除了函数对象,还可以使用 Lambda 表达式 (lambda expressions) 作为 shared_ptr 的自定义删除器。Lambda 表达式提供了一种更简洁、内联的方式来定义删除器,特别适用于简单的删除操作。(In addition to function objects, lambda expressions can also be used as custom deleters for shared_ptr. Lambda expressions provide a more concise, inline way to define deleters, especially suitable for simple deletion operations.)

    使用 Lambda 表达式作为删除器,可以将删除逻辑直接嵌入到 shared_ptr 的创建代码中,代码更加简洁易读。(Using lambda expressions as deleters, the deletion logic can be directly embedded into the shared_ptr creation code, making the code more concise and readable.)

    以下示例使用 Lambda 表达式作为删除器,实现与上例相同的文件句柄管理功能:(The following example uses a lambda expression as a deleter to achieve the same file handle management functionality as the previous example:)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <cstdio>
    4
    5 int main() {
    6 FILE* fp = fopen("example.txt", "r");
    7 if (!fp) {
    8 std::cerr << "Failed to open file." << std::endl;
    9 return 1;
    10 }
    11
    12 std::shared_ptr<FILE> file_ptr(fp, [](FILE* f) { // Lambda 表达式作为删除器 (Lambda expression as deleter)
    13 if (f) {
    14 std::cout << "Closing file handle using lambda." << std::endl;
    15 fclose(f);
    16 }
    17 });
    18
    19 // ... 使用 file_ptr 操作文件 (Use file_ptr to operate on the file)
    20 char buffer[100];
    21 if (fgets(buffer, sizeof(buffer), file_ptr.get())) {
    22 std::cout << "Read from file: " << buffer << std::endl;
    23 }
    24
    25 return 0;
    26 } // file_ptr 析构,Lambda 表达式被调用,文件句柄被关闭 (file_ptr is destructed, lambda expression is called, file handle is closed)

    在这个例子中,Lambda 表达式 [](FILE* f) { ... } 被直接作为删除器传递给 shared_ptr 的构造函数。Lambda 表达式的功能与之前的 FileCloser 函数对象相同,都是关闭文件句柄。使用 Lambda 表达式可以避免定义额外的函数对象类,使代码更加紧凑。(In this example, the lambda expression [](FILE* f) { ... } is directly passed as a deleter to the shared_ptr constructor. The function of the lambda expression is the same as the previous FileCloser function object, which is to close the file handle. Using lambda expressions can avoid defining additional function object classes and make the code more compact.)

    3.4.3 删除器与类型擦除 (Deleters and Type Erasure)

    当使用自定义删除器时,shared_ptr 需要存储删除器的信息,以便在适当的时候调用它。为了实现类型擦除 (type erasure)shared_ptr 的实现通常使用小对象优化 (Small Object Optimization, SBO) 或类似的技巧来存储删除器。类型擦除允许 shared_ptr 在保持类型安全的同时,能够接受各种不同类型的删除器,而无需使用模板或虚函数。(When using custom deleters, shared_ptr needs to store information about the deleter so that it can be called at the appropriate time. To achieve type erasure, the implementation of shared_ptr typically uses Small Object Optimization (SBO) or similar techniques to store the deleter. Type erasure allows shared_ptr to accept various types of deleters while maintaining type safety, without using templates or virtual functions.)

    类型擦除的意义 (Significance of Type Erasure)

    灵活性 (Flexibility):允许使用各种类型的删除器,包括函数指针、函数对象、Lambda 表达式等,增加了 shared_ptr 的使用灵活性。(Flexibility: Allows the use of various types of deleters, including function pointers, function objects, lambda expressions, etc., increasing the flexibility of using shared_ptr.)

    编译期类型安全 (Compile-time Type Safety):类型擦除是在编译期完成的,仍然保持了 C++ 的编译期类型安全特性。(Compile-time Type Safety: Type erasure is done at compile time, still maintaining the compile-time type safety features of C++.)

    避免虚函数开销 (Avoids Virtual Function Overhead):与使用虚函数实现多态删除相比,类型擦除通常具有更低的运行时开销,因为它避免了虚函数调用的间接性。(Avoids Virtual Function Overhead: Compared to using virtual functions to implement polymorphic deletion, type erasure usually has lower runtime overhead because it avoids the indirection of virtual function calls.)

    实现方式 (Implementation Methods)

    shared_ptr 的控制块通常会预留一部分空间来存储删除器。如果删除器对象足够小 (例如,函数指针或简单的 Lambda 表达式),可以直接将删除器对象的值存储在控制块的预留空间中 (SBO)。如果删除器对象较大,则可能需要动态分配内存来存储删除器,并在控制块中存储指向删除器的指针。具体的实现细节会因编译器和标准库版本而异。(The control block of shared_ptr usually reserves some space to store the deleter. If the deleter object is small enough (e.g., a function pointer or a simple lambda expression), the value of the deleter object can be directly stored in the reserved space of the control block (SBO). If the deleter object is large, it may be necessary to dynamically allocate memory to store the deleter and store a pointer to the deleter in the control block. Specific implementation details will vary depending on the compiler and standard library version.)

    总而言之,自定义删除器是 shared_ptr 强大的资源管理特性,它使得 shared_ptr 不仅可以管理动态分配的内存,还可以管理各种类型的资源,并提供灵活的资源释放策略。(In summary, custom deleters are a powerful resource management feature of shared_ptr, which enables shared_ptr to manage not only dynamically allocated memory, but also various types of resources, and provides flexible resource release strategies.)

    3.5 别名 shared_ptr (Aliasing shared_ptr)

    别名 shared_ptr (aliasing shared_ptr) 是一种特殊的 shared_ptr,它共享所有权 (shared ownership),但指向的对象 (或子对象) 与原始 shared_ptr 指向的对象不同。别名 shared_ptr 允许在保持对象生命周期管理的同时,访问对象的不同部分或相关对象。(Aliasing shared_ptr is a special type of shared_ptr that shares ownership, but the object (or subobject) it points to is different from the object pointed to by the original shared_ptr. Aliasing shared_ptr allows accessing different parts or related objects of an object while maintaining object lifecycle management.)

    3.5.1 别名构造函数 (Aliasing Constructor)

    shared_ptr 提供了一种别名构造函数 (aliasing constructor),用于创建别名 shared_ptr。别名构造函数的原型如下:( shared_ptr provides an aliasing constructor for creating aliasing shared_ptr instances. The prototype of the aliasing constructor is as follows:)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 template<typename Y, typename D>
    2 shared_ptr(const shared_ptr<Y>& r, T* ptr) noexcept;

    这个构造函数接受两个参数:

    r: 一个已有的 shared_ptr<Y> 实例,表示共享所有权的来源。别名 shared_ptr 将与 r 共享同一个控制块,引用计数也会与 r 共享。( r: An existing shared_ptr<Y> instance, indicating the source of shared ownership. The aliasing shared_ptr will share the same control block with r, and the reference count will also be shared with r.)

    ptr: 一个裸指针 T*,表示别名 shared_ptr 实际指向的对象ptr 可以是 r 所管理对象的一部分,也可以是与 r 所管理对象相关的另一个对象。( ptr: A raw pointer T*, indicating the object that the aliasing shared_ptr actually points to. ptr can be part of the object managed by r, or another object related to the object managed by r.)

    关键点 (Key Points)

    ① 别名 shared_ptr 不拥有 ptr 指向的对象的所有权。它与 r 共享所有权,真正被管理的对象仍然是 r 指向的对象。(The aliasing shared_ptr does not own the ownership of the object pointed to by ptr. It shares ownership with r, and the object that is truly managed is still the object pointed to by r.)

    ② 别名 shared_ptr 的析构 不会 导致 ptr 指向的对象的析构。只有当与别名 shared_ptr 共享控制块的所有 shared_ptr (包括 r 和所有通过 r 创建的别名 shared_ptr) 都被析构时,才会触发删除器,删除 r 所管理的对象。(The destruction of the aliasing shared_ptr does not lead to the destruction of the object pointed to by ptr. Only when all shared_ptr instances sharing the control block with the aliasing shared_ptr (including r and all aliasing shared_ptr instances created through r) are destructed, will the deleter be triggered to delete the object managed by r.)

    ③ 别名 shared_ptr 的类型是 shared_ptr<T>,其中 Tptr 指针的类型。r 的类型 shared_ptr<Y> 可以与 shared_ptr<T> 不同。(The type of the aliasing shared_ptr is shared_ptr<T>, where T is the type of the ptr pointer. The type shared_ptr<Y> of r can be different from shared_ptr<T>.)

    3.5.2 应用场景 (Use Cases)

    别名 shared_ptr 在以下场景中非常有用:(Aliasing shared_ptr is very useful in the following scenarios:)

    访问对象的成员或子对象 (Accessing Members or Subobjects of an Object):当需要共享一个对象的生命周期,但只需要访问对象的某个成员或子对象时,可以使用别名 shared_ptr。例如,一个类 Outer 包含一个成员 Inner,可以使用 shared_ptr<Outer> 管理 Outer 对象的生命周期,并使用 shared_ptr<Inner> 作为别名 shared_ptr 指向 Outer 对象的 Inner 成员。这样,即使只持有 shared_ptr<Inner>Outer 对象也不会被提前析构。(Accessing Members or Subobjects of an Object: When you need to share the lifecycle of an object, but only need to access a member or subobject of the object, you can use aliasing shared_ptr. For example, if a class Outer contains a member Inner, you can use shared_ptr<Outer> to manage the lifecycle of the Outer object, and use shared_ptr<Inner> as an aliasing shared_ptr pointing to the Inner member of the Outer object. In this way, even if only shared_ptr<Inner> is held, the Outer object will not be destructed prematurely.)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class Inner {
    5 public:
    6 void inner_method() {
    7 std::cout << "Inner method called." << std::endl;
    8 }
    9 };
    10
    11 class Outer {
    12 public:
    13 Outer() : inner_(std::make_shared<Inner>()) {} // 使用 shared_ptr 管理 Inner 成员 (Use shared_ptr to manage Inner member - incorrect in aliasing context, should be raw ptr)
    14 std::shared_ptr<Inner> get_inner_ptr() const { return inner_; } // 返回指向 Inner 成员的 shared_ptr (Returns a shared_ptr to the Inner member - incorrect in aliasing context)
    15 Inner* get_inner_raw_ptr() const { return inner_.get(); } // 返回指向 Inner 成员的 raw pointer (Returns a raw pointer to the Inner member - correct for aliasing)
    16
    17
    18 private:
    19 std::shared_ptr<Inner> inner_; // 指向 Inner 对象的 shared_ptr 成员 - should be raw pointer in aliasing context to demonstrate aliasing correctly
    20 };
    21
    22 int main() {
    23 auto outer_ptr = std::make_shared<Outer>(); // shared_ptr 管理 Outer 对象 (shared_ptr manages Outer object)
    24
    25 // std::shared_ptr<Inner> inner_alias_ptr = outer_ptr->get_inner_ptr(); // 错误:这只是普通的 shared_ptr 拷贝 (Incorrect: This is just a normal shared_ptr copy)
    26
    27 std::shared_ptr<Inner> inner_alias_ptr(outer_ptr, outer_ptr->get_inner_raw_ptr()); // 正确:使用别名构造函数 (Correct: Use aliasing constructor)
    28
    29
    30 outer_ptr.reset(); // 原始 shared_ptr 释放所有权 (Original shared_ptr releases ownership)
    31 // 但 Outer 对象不会被析构,因为 inner_alias_ptr 仍然持有所有权 (But Outer object will not be destructed because inner_alias_ptr still holds ownership)
    32
    33 inner_alias_ptr->inner_method(); // 可以正常访问 Inner 对象 (Can still access Inner object normally)
    34
    35 std::cout << "Inner alias ptr use count: " << inner_alias_ptr.use_count() << std::endl; // 引用计数仍然为 1 (Reference count is still 1)
    36
    37
    38 return 0;
    39 } // inner_alias_ptr 析构,Outer 对象和 Inner 对象被析构 (inner_alias_ptr is destructed, Outer object and Inner object are destructed)

    更正后的代码示例 (Corrected Code Example):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class Inner {
    5 public:
    6 void inner_method() {
    7 std::cout << "Inner method called." << std::endl;
    8 }
    9 };
    10
    11 class Outer {
    12 public:
    13 Outer() : inner_(new Inner()) {}
    14 ~Outer() { delete inner_; } // 手动管理 Inner 对象的生命周期 (Manually manage Inner object's lifecycle)
    15 Inner* get_inner_raw_ptr() const { return inner_; } // 返回指向 Inner 成员的 raw pointer (Returns a raw pointer to the Inner member)
    16
    17 private:
    18 Inner* inner_; // 指向 Inner 对象的 raw pointer 成员 (Raw pointer member to Inner object)
    19 };
    20
    21 int main() {
    22 auto outer_ptr = std::make_shared<Outer>(); // shared_ptr 管理 Outer 对象 (shared_ptr manages Outer object)
    23
    24 std::shared_ptr<Inner> inner_alias_ptr(outer_ptr, outer_ptr->get_inner_raw_ptr()); // 正确:使用别名构造函数 (Correct: Use aliasing constructor)
    25
    26 outer_ptr.reset(); // 原始 shared_ptr 释放所有权 (Original shared_ptr releases ownership)
    27 // 但 Outer 对象不会被析构,因为 inner_alias_ptr 仍然持有所有权 (But Outer object will not be destructed because inner_alias_ptr still holds ownership)
    28
    29 inner_alias_ptr->inner_method(); // 可以正常访问 Inner 对象 (Can still access Inner object normally)
    30
    31 std::cout << "Inner alias ptr use count: " << inner_alias_ptr.use_count() << std::endl;
    32
    33
    34 return 0;
    35 } // inner_alias_ptr 析构,Outer 对象和 Inner 对象被析构 (inner_alias_ptr is destructed, Outer object and Inner object are destructed)

    工厂模式 (Factory Pattern):在工厂模式中,工厂函数可能返回指向对象的某个特定接口或基类的指针,但仍然需要保持对整个对象生命周期的管理。别名 shared_ptr 可以用于返回指向接口或基类的 shared_ptr,同时保持对实际派生类对象的所有权。(Factory Pattern: In factory patterns, factory functions may return pointers to a specific interface or base class of an object, but still need to maintain management of the entire object lifecycle. Aliasing shared_ptr can be used to return a shared_ptr pointing to an interface or base class, while maintaining ownership of the actual derived class object.)

    观察者模式 (Observer Pattern):在观察者模式中,观察者可能需要持有对主题对象 (Subject) 的引用,但又不希望影响主题对象的生命周期。可以使用 weak_ptr 避免循环引用,同时,如果需要在观察者中安全地访问主题对象,可以使用别名 shared_ptrweak_ptr 提升 (promote) 为 shared_ptr,并指向主题对象的某个特定部分。(Observer Pattern: In the observer pattern, observers may need to hold a reference to the subject object, but do not want to affect the lifecycle of the subject object. weak_ptr can be used to avoid circular references, and if it is necessary to safely access the subject object in the observer, aliasing shared_ptr can be used to promote from weak_ptr to shared_ptr and point to a specific part of the subject object.)

    别名 shared_ptr 是一种高级技巧,需要谨慎使用。它在某些特定场景下非常有用,可以提高代码的灵活性和表达力。(Aliasing shared_ptr is an advanced technique that needs to be used with caution. It is very useful in certain specific scenarios and can improve the flexibility and expressiveness of the code.)

    4. shared_ptr 的高级应用 (Advanced Applications of shared_ptr)

    本章 explores advanced applications of shared_ptr in various contexts, including containers, 多态 (polymorphism), 工厂模式 (factory patterns), and Pimpl 惯用法 (Pimpl idiom)。 (本章探讨了 shared_ptr 在各种上下文中的高级应用,包括容器、多态、工厂模式和 Pimpl 惯用法。)

    4.1 shared_ptr 与容器 (shared_ptr and Containers)

    本节 discusses how to use shared_ptr effectively with standard C++ 容器 (containers) like std::vector, std::list, and std::map. (本节讨论了如何有效地将 shared_ptr 与标准 C++ 容器(如 std::vectorstd::liststd::map)一起使用。)

    4.1.1 在容器中存储 shared_ptr (Storing shared_ptr in Containers)

    Explains best practices for storing shared_ptr in 容器 (containers) and managing the lifetime of objects within 容器 (containers). (解释了在容器中存储 shared_ptr 以及管理容器内对象生命周期的最佳实践。)

    在现代 C++ 编程中,将 shared_ptr 存储在 容器 (containers) 中是一种常见的且强大的技术,尤其是在需要共享对象所有权和自动内存管理的场景下。shared_ptr 与标准库 容器 (containers)(如 std::vector, std::list, std::map, std::set 等)能够良好地协同工作,为复杂的数据结构和应用程序提供了灵活且安全的内存管理方案。

    直接存储 shared_ptr 对象: 最直接的方法是将 shared_ptr 对象本身存储在 容器 (containers) 中。这适用于当你希望 容器 (containers) 中的元素共享对象所有权的情况。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <vector>
    2 #include <memory>
    3
    4 int main() {
    5 std::vector<std::shared_ptr<int>> sharedPtrVector;
    6
    7 // 创建一些 shared_ptr 并存储在 vector 中
    8 sharedPtrVector.push_back(std::make_shared<int>(10));
    9 sharedPtrVector.push_back(std::make_shared<int>(20));
    10 sharedPtrVector.push_back(std::make_shared<int>(30));
    11
    12 // 可以通过 vector 访问共享的对象
    13 for (const auto& ptr : sharedPtrVector) {
    14 std::cout << *ptr << " "; // 输出:10 20 30
    15 }
    16 std::cout << std::endl;
    17
    18 return 0;
    19 }

    在这个例子中,sharedPtrVector 是一个存储 std::shared_ptr<int> 类型的 std::vector。每个 shared_ptr 都拥有对 int 对象的共享所有权。当 sharedPtrVector 销毁或者元素被移除时,shared_ptr 的析构函数会被调用,引用计数会相应减少。当最后一个指向 int 对象的 shared_ptr 销毁时,int 对象的内存才会被释放。

    优势:

    ▮▮▮▮ⓐ 自动内存管理: 这是使用 shared_ptr 的核心优势。存储在 容器 (containers) 中的 shared_ptr 负责管理其指向对象的生命周期。当 容器 (containers) 销毁或元素被移除时,相关的内存会自动释放,无需手动 delete,从而有效避免内存泄漏。
    ▮▮▮▮ⓑ 共享所有权: 多个 shared_ptr 可以指向同一个对象,共享对象的所有权。这意味着对象会在所有指向它的 shared_ptr 都销毁后才被删除。这在复杂的对象关系和数据结构中非常有用,例如图形结构、缓存管理等。
    ▮▮▮▮ⓒ 异常安全: 由于内存管理是自动的,即使在异常抛出的情况下,通过 RAII (Resource Acquisition Is Initialization) 原则,shared_ptr 也能确保资源被正确释放,避免资源泄漏。

    最佳实践:

    ▮▮▮▮ⓐ 优先使用 make_shared: 使用 std::make_shared 创建 shared_ptr 比直接使用 new 并传递给 shared_ptr 构造函数更高效且异常安全。make_shared 能够一次性分配控制块和对象内存,减少内存分配次数,并提高性能。此外,它还能防止在 new 操作和 shared_ptr 构造函数之间抛出异常而导致的资源泄漏。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 推荐做法:使用 make_shared
    2 sharedPtrVector.push_back(std::make_shared<int>(40));
    3
    4 // 不推荐做法:分离 new 和 shared_ptr 构造
    5 sharedPtrVector.push_back(std::shared_ptr<int>(new int(50))); // 效率较低,异常安全性稍差

    ▮▮▮▮ⓑ 合理管理所有权: 当在 容器 (containers) 中存储 shared_ptr 时,需要仔细考虑所有权模型。确保对象的所有权关系清晰,避免出现意外的生命周期问题。如果仅仅是需要在 容器 (containers) 中存储对象的引用,但不希望共享所有权,可以考虑使用原始指针(谨慎使用)或 弱指针 (weak_ptr)。然而,在大多数现代 C++ 应用中,为了安全和方便,shared_ptr 通常是更好的选择。
    ▮▮▮▮ⓒ 避免循环引用: 当 容器 (containers) 中存储的 shared_ptr 所指向的对象之间存在相互引用关系时,可能会导致循环引用问题。循环引用会阻止引用计数降为零,从而导致内存泄漏。在这种情况下,应该使用 弱指针 (weak_ptr) 来打破循环引用,详情请参考后续章节关于 weak_ptr 的讨论。
    ▮▮▮▮ⓓ 考虑值语义与指针语义: 将 shared_ptr 存储在 容器 (containers) 中,实际上是在 容器 (containers) 中存储指针语义的对象。容器存储的是 shared_ptr 的副本,所有副本共享同一个底层对象的所有权。这与直接存储值语义的对象不同,需要根据实际需求选择合适的方式。如果需要值语义,可能需要考虑在 容器 (containers) 中直接存储对象,或者使用 std::unique_ptr 如果所有权不需要共享。

    示例:使用 std::map 存储 shared_ptr: std::map 容器 (containers) 也可以很好地与 shared_ptr 结合使用,例如创建一个对象缓存,其中键 (key) 是对象的标识符,值 (value) 是指向对象的 shared_ptr

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <map>
    2 #include <string>
    3 #include <memory>
    4
    5 class DataObject {
    6 public:
    7 DataObject(std::string name) : name_(name) {
    8 std::cout << "DataObject " << name_ << " created." << std::endl;
    9 }
    10 ~DataObject() {
    11 std::cout << "DataObject " << name_ << " destroyed." << std::endl;
    12 }
    13 std::string getName() const { return name_; }
    14 private:
    15 std::string name_;
    16 };
    17
    18 int main() {
    19 std::map<std::string, std::shared_ptr<DataObject>> objectCache;
    20
    21 // 创建并缓存 DataObject 对象
    22 objectCache["object1"] = std::make_shared<DataObject>("Object 1");
    23 objectCache["object2"] = std::make_shared<DataObject>("Object 2");
    24
    25 // 从缓存中获取对象
    26 auto obj1Ptr = objectCache["object1"];
    27 if (obj1Ptr) {
    28 std::cout << "Retrieved object from cache: " << obj1Ptr->getName() << std::endl;
    29 }
    30
    31 // 当 objectCache 超出作用域,所有 shared_ptr 都会被销毁,
    32 // 并且它们指向的 DataObject 对象也会被自动删除。
    33 return 0;
    34 }

    在这个例子中,objectCache 是一个 std::map,它将字符串键 (key) 映射到 std::shared_ptr<DataObject>。当 objectCache 容器 (containers) 销毁时,存储在 map 中的所有 shared_ptr 都会被销毁,从而自动释放 DataObject 对象的内存。这展示了 shared_ptr 在 容器 (containers) 中存储和管理动态分配对象生命周期的有效性和便利性。

    总之,将 shared_ptr 存储在 C++ 标准库 容器 (containers) 中是现代 C++ 编程中管理对象生命周期和共享所有权的一种强大而安全的方法。通过合理地使用 shared_ptr 和遵循最佳实践,可以编写出更健壮、更易于维护且内存安全的代码。

    4.1.2 容器与所有权转移 (Containers and Ownership Transfer)

    Discusses how 容器 (container) operations like 移动语义 (move semantics) interact with shared_ptr and 所有权转移 (ownership transfer). (讨论了容器操作(如移动语义)如何与 shared_ptr 和所有权转移交互。)

    移动语义 (move semantics) 是 C++11 引入的核心特性,旨在提高性能,尤其是在处理资源管理和对象复制时。当涉及到存储 shared_ptr 的 容器 (containers) 时,理解 移动语义 (move semantics) 如何与 shared_ptr 交互以及如何影响 所有权转移 (ownership transfer) 至关重要。

    移动语义 (Move Semantics) 的基本概念:

    移动语义 (move semantics) 允许资源的所有权从一个对象转移到另一个对象,而无需执行深拷贝。这对于管理动态分配的资源(如内存)非常有效。在 容器 (containers) 中,当插入、删除或重新分配元素时,移动语义 (move semantics) 可以显著提高效率。

    C++ 中与 移动语义 (move semantics) 相关的关键概念包括:

    ▮▮▮▮ⓐ 移动构造函数 (Move Constructor): 用于从一个右值引用 (rvalue reference) 对象构造新对象。移动构造函数会将源对象的资源“移动”到新对象,而不是复制。
    ▮▮▮▮ⓑ 移动赋值运算符 (Move Assignment Operator): 用于将一个右值引用 (rvalue reference) 对象赋值给已存在的对象。与移动构造函数类似,它也会将源对象的资源“移动”到目标对象。
    ▮▮▮▮ⓒ 右值引用 (Rvalue Reference): 使用 && 声明,用于绑定到临时对象或即将销毁的对象(右值)。移动操作通常针对右值引用 (rvalue reference) 对象进行。
    ▮▮▮▮ⓓ std::move: 一个实用工具函数,用于将左值 (lvalue) 转换为右值引用 (rvalue reference),从而可以对左值 (lvalue) 对象应用移动操作。需要注意的是,std::move 本身并不执行移动操作,它只是一个类型转换,使得对象可以被移动。

    shared_ptr 的移动语义 (Move Semantics of shared_ptr):

    shared_ptr 很好地支持 移动语义 (move semantics)。移动 shared_ptr 对象是一个低开销的操作,因为它只涉及到内部指针和引用计数的移动,而不需要复制所管理的对象。当一个 shared_ptr 被移动时,其内部指针和控制块 (control block) 的所有权会被转移到新的 shared_ptr,而引用计数不会改变。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <vector>
    2 #include <memory>
    3
    4 int main() {
    5 std::vector<std::shared_ptr<int>> vec1;
    6 vec1.push_back(std::make_shared<int>(100));
    7
    8 // 移动构造 vector
    9 std::vector<std::shared_ptr<int>> vec2 = std::move(vec1);
    10
    11 // 移动赋值 vector
    12 std::vector<std::shared_ptr<int>> vec3;
    13 vec3 = std::move(vec2);
    14
    15 // 此时 vec3 拥有之前 vec1 和 vec2 中的 shared_ptr,而 vec1 和 vec2 变为空
    16 std::cout << *vec3[0] << std::endl; // 输出:100
    17 std::cout << "vec1 size: " << vec1.size() << std::endl; // 输出:vec1 size: 0
    18 std::cout << "vec2 size: " << vec2.size() << std::endl; // 输出:vec2 size: 0
    19 std::cout << "vec3 size: " << vec3.size() << std::endl; // 输出:vec3 size: 1
    20
    21 return 0;
    22 }

    在这个例子中,当 vec1 移动到 vec2vec3 时,存储在 vec1 中的 shared_ptr 对象的所有权被转移到了 vec2vec3vec1vec2 移动后变为空,但它们原来包含的 shared_ptr 所指向的 int 对象仍然被 vec3 中的 shared_ptr 所管理,且引用计数保持不变。

    容器操作与 shared_ptr 的所有权转移 (Ownership Transfer in Container Operations):

    标准库 容器 (containers) 的许多操作都利用 移动语义 (move semantics) 来提高效率,这对于存储 shared_ptr 的 容器 (containers) 尤其重要,因为移动 shared_ptr 的开销远小于复制。

    ▮▮▮▮ⓐ 插入操作 (push_back, emplace_back, insert, emplace): 当使用这些操作向 容器 (containers) 中插入 shared_ptr 时,通常会发生移动操作(如果插入的是右值引用 (rvalue reference) 或使用 std::move)。这意味着插入操作不会增加引用计数,而是将 shared_ptr 的所有权从源对象转移到 容器 (containers) 内部。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::vector<std::shared_ptr<DataObject>> dataVec;
    2 std::shared_ptr<DataObject> objPtr = std::make_shared<DataObject>("Initial Object");
    3
    4 dataVec.push_back(objPtr); // 复制 shared_ptr,引用计数增加
    5 dataVec.push_back(std::move(objPtr)); // 移动 shared_ptr,所有权转移,objPtr 变为空

    在上面的代码中,第一次 push_back 操作复制了 objPtr,引用计数增加。第二次 push_back 操作使用 std::move(objPtr),移动了 objPtr,所有权转移到 dataVec 中,objPtr 自身变为空(或者处于有效但未指定的状态)。

    ▮▮▮▮ⓑ 构造和赋值操作 (Container Construction and Assignment): 容器 (containers) 的移动构造函数 (move constructor) 和移动赋值运算符 (move assignment operator) 会将其内部存储的 shared_ptr 元素也进行移动操作。这避免了深拷贝,提高了容器 (containers) 整体的构造和赋值效率。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::vector<std::shared_ptr<int>> originalVec;
    2 originalVec.push_back(std::make_shared<int>(1));
    3 originalVec.push_back(std::make_shared<int>(2));
    4
    5 // 移动构造
    6 std::vector<std::shared_ptr<int>> movedVec = std::move(originalVec);
    7 // movedVec 现在拥有 originalVec 的资源,originalVec 变为空
    8
    9 // 移动赋值
    10 std::vector<std::shared_ptr<int>> anotherVec;
    11 anotherVec = std::move(movedVec);
    12 // anotherVec 现在拥有 movedVec 的资源,movedVec 变为空

    ▮▮▮▮ⓒ 范围 for 循环 (Range-based for loop): 在使用范围 for 循环遍历存储 shared_ptr 的 容器 (containers) 时,需要注意循环变量的类型。如果使用值类型 (value type) 迭代,会复制 shared_ptr,增加引用计数。如果使用引用类型 (reference type) 或常量引用类型 (const reference type),则不会增加引用计数。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::vector<std::shared_ptr<int>> numVec;
    2 numVec.push_back(std::make_shared<int>(10));
    3 numVec.push_back(std::make_shared<int>(20));
    4
    5 // 使用值类型迭代,复制 shared_ptr
    6 for (auto ptr : numVec) { // 每次迭代复制 shared_ptr
    7 std::cout << *ptr << " ";
    8 }
    9 std::cout << std::endl;
    10
    11 // 使用常量引用类型迭代,不复制 shared_ptr
    12 for (const auto& ptr : numVec) { // 每次迭代使用引用,不复制 shared_ptr
    13 std::cout << *ptr << " ";
    14 }
    15 std::cout << std::endl;

    最佳实践和注意事项:

    ▮▮▮▮ⓐ 明确所有权转移意图: 当使用 std::move 显式地进行移动操作时,应明确表达 所有权转移 (ownership transfer) 的意图。移动后,源对象通常应被视为处于有效但未指定的状态,不应再依赖其原有的值。
    ▮▮▮▮ⓑ 利用移动语义提高性能: 在需要频繁插入、删除或移动 容器 (containers) 元素,且元素为 shared_ptr 时,充分利用 移动语义 (move semantics) 可以显著提高性能。例如,在函数间传递存储 shared_ptr 的 容器 (containers) 时,应优先使用移动操作而不是复制操作。
    ▮▮▮▮ⓒ 避免移动后使用源对象: 移动操作后,源对象的状态是不确定的。虽然对于 shared_ptr 来说,移动后源 shared_ptr 可能会变为空,但一般不应再依赖移动前的源对象。
    ▮▮▮▮ⓓ 理解 容器 (containers) 的内部机制: 理解不同 容器 (containers) 操作如何处理元素(复制、移动、销毁)对于正确使用 shared_ptr 至关重要。例如,std::vector 在容量不足时重新分配内存可能会涉及到元素的移动或复制。

    总结来说,移动语义 (move semantics) 与 shared_ptr 的结合使用是 C++ 中高效资源管理的关键。在 容器 (containers) 中存储和操作 shared_ptr 时,理解 移动语义 (move semantics) 如何影响 所有权转移 (ownership transfer) 以及如何利用移动操作来提高性能,对于编写高效、安全的代码至关重要。通过合理运用移动语义 (move semantics),可以最大限度地减少不必要的资源复制,提升程序的整体性能。

    4.2 shared_ptr 与多态 (shared_ptr and Polymorphism)

    本节 explains how shared_ptr works with 多态 (polymorphic) types and the importance of 虚析构函数 (virtual destructors) in such scenarios. (本节解释了 shared_ptr 如何与多态类型一起工作,以及虚拟析构函数在此类场景中的重要性。)

    多态 (polymorphism) 是面向对象编程 (Object-Oriented Programming, OOP) 的核心概念之一,允许将派生类 (derived class) 对象视为基类 (base class) 对象来处理。shared_ptr 与 多态 (polymorphism) 结合使用时,能够安全地管理多态对象的生命周期,尤其是在通过基类 (base class) 指针操作派生类 (derived class) 对象时, 虚析构函数 (virtual destructors) 起着至关重要的作用。

    4.2.1 虚析构函数的重要性 (Importance of Virtual Destructors)

    Highlights why 虚析构函数 (virtual destructors) are crucial when using shared_ptr with 基类 (base class) 指针 to 派生类 (derived class) objects. (强调了当使用 shared_ptr 与基类指针指向派生类对象时,虚拟析构函数为何至关重要。)

    多态 (Polymorphism) 与对象析构的问题:

    当使用 基类 (base class) 指针指向 派生类 (derived class) 对象时,通过 基类 (base class) 指针删除对象,如果没有 虚析构函数 (virtual destructors),则只会调用 基类 (base class) 的析构函数,而不会调用 派生类 (derived class) 的析构函数。这会导致 部分析构 (partial destruction) 的问题,即 派生类 (derived class) 特有的资源可能无法被正确释放,从而造成资源泄漏或其他未定义行为。

    考虑以下代码示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class Base {
    5 public:
    6 Base() { std::cout << "Base constructor" << std::endl; }
    7 ~Base() { std::cout << "Base destructor" << std::endl; } // 非虚析构函数
    8 };
    9
    10 class Derived : public Base {
    11 public:
    12 Derived() { std::cout << "Derived constructor" << std::endl; }
    13 ~Derived() { std::cout << "Derived destructor" << std::endl; }
    14 };
    15
    16 int main() {
    17 Base* basePtr = new Derived(); // 基类指针指向派生类对象
    18 delete basePtr; // 通过基类指针删除对象
    19 return 0;
    20 }

    这段代码的输出可能是:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Base constructor
    2 Derived constructor
    3 Base destructor

    可以看到,Derived 的构造函数和 Base 的构造函数都被调用了,但是只有 Base 的析构函数被调用,Derived 的析构函数没有被执行。这是因为 Base 类的析构函数不是虚函数 (virtual function)。当通过 Base* 指针删除 Derived 对象时,只会根据指针的类型(Base*)调用析构函数,而忽略了对象的实际类型(Derived)。

    虚析构函数 (Virtual Destructors) 的作用:

    将 基类 (base class) 的析构函数声明为 虚函数 (virtual function) 可以解决上述问题。当析构函数为虚函数 (virtual function) 时,通过 基类 (base class) 指针删除 派生类 (derived class) 对象时,会发生 动态绑定 (dynamic binding),实际调用的是 派生类 (derived class) 的析构函数,然后 隐式地 (implicitly) 调用 基类 (base class) 的析构函数,从而确保整个对象的析构过程正确执行。

    修改上面的代码,将 Base 类的析构函数声明为虚函数 (virtual function):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class Base {
    5 public:
    6 Base() { std::cout << "Base constructor" << std::endl; }
    7 virtual ~Base() { std::cout << "Base destructor" << std::endl; } // 虚析构函数
    8 };
    9
    10 class Derived : public Base {
    11 public:
    12 Derived() { std::cout << "Derived constructor" << std::endl; }
    13 ~Derived() { std::cout << "Derived destructor" << std::endl; }
    14 };
    15
    16 int main() {
    17 Base* basePtr = new Derived(); // 基类指针指向派生类对象
    18 delete basePtr; // 通过基类指针删除对象
    19 return 0;
    20 }

    此时的输出将是:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Base constructor
    2 Derived constructor
    3 Derived destructor
    4 Base destructor

    现在,DerivedBase 的析构函数都正确地被调用了,避免了部分析构 (partial destruction) 的问题。

    shared_ptr 与 虚析构函数 (Virtual Destructors):

    当使用 shared_ptr 管理多态对象时, 虚析构函数 (virtual destructors) 的重要性依然存在。shared_ptr 在释放所管理的对象时,会调用对象的析构函数。如果 派生类 (derived class) 对象是通过 shared_ptr<Base> 管理的,而 Base 类没有 虚析构函数 (virtual destructors),那么当 shared_ptr<Base> 析构时,可能只会调用 Base 的析构函数,导致 派生类 (derived class) 的资源泄漏。

    因此,当基类 (base class) 可能被用作多态基类时,即可能通过 基类 (base class) 指针指向 派生类 (derived class) 对象时,务必将 基类 (base class) 的析构函数声明为虚函数 (virtual function)。这对于使用 shared_ptr 安全管理多态对象至关重要。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class Base {
    5 public:
    6 Base() { std::cout << "Base constructor" << std::endl; }
    7 virtual ~Base() { std::cout << "Base destructor" << std::endl; } // 虚析构函数
    8 };
    9
    10 class Derived : public Base {
    11 public:
    12 Derived() { std::cout << "Derived constructor" << std::endl; }
    13 ~Derived() { std::cout << "Derived destructor" << std::endl; }
    14 };
    15
    16 int main() {
    17 std::shared_ptr<Base> smartBasePtr = std::make_shared<Derived>(); // shared_ptr<Base> 管理 Derived 对象
    18 // 当 smartBasePtr 超出作用域时,Derived 和 Base 的析构函数都会被调用
    19 return 0;
    20 }

    在这个例子中,smartBasePtr 是一个 std::shared_ptr<Base>,它管理着一个 Derived 对象。由于 Base 类有 虚析构函数 (virtual destructors),当 smartBasePtr 析构时,会正确地调用 Derived 的析构函数,然后再调用 Base 的析构函数,确保多态对象的析构过程正确无误。

    总结:

    虚析构函数 (virtual destructors) 在多态 (polymorphism) 和资源管理中扮演着关键角色。当基类 (base class) 可能被用作多态基类时,务必将其析构函数声明为 virtual。这不仅对于手动内存管理(使用 newdelete)至关重要,而且对于使用智能指针(如 shared_ptr)进行自动资源管理同样重要。通过 虚析构函数 (virtual destructors),可以确保在使用 基类 (base class) 指针操作 派生类 (derived class) 对象时,能够正确地析构整个对象,避免资源泄漏和未定义行为,从而保证程序的健壮性和可靠性。

    4.2.2 多态删除 (Polymorphic Deletion)

    Explains how shared_ptr ensures correct 多态删除 (polymorphic deletion) when managing objects through 基类 (base class) pointers. (解释了当通过基类指针管理对象时,shared_ptr 如何确保正确的多态删除。)

    多态删除 (polymorphic deletion) 指的是通过 基类 (base class) 指针删除 派生类 (derived class) 对象时,能够正确调用 派生类 (derived class) 的析构函数,进而确保整个对象(包括基类 (base class) 部分和派生类 (derived class) 部分)被完整地析构和资源被正确释放。shared_ptr 通过与 虚析构函数 (virtual destructors) 协同工作,实现了安全的多态删除 (polymorphic deletion)。

    shared_ptr 如何实现多态删除 (How shared_ptr Achieves Polymorphic Deletion):

    当使用 shared_ptr 管理对象时,析构操作是由 shared_ptr 自动完成的。当最后一个指向某个对象的 shared_ptr 实例被销毁时,shared_ptr 会负责删除它所管理的对象。对于多态对象,如果 基类 (base class) 具有 虚析构函数 (virtual destructors),那么通过 shared_ptr<Base> 管理的 派生类 (derived class) 对象就能被正确地多态删除 (polymorphic deletion)。

    以下是 shared_ptr 实现多态删除 (polymorphic deletion) 的关键机制:

    ▮▮▮▮ⓐ 虚析构函数 (Virtual Destructor) 的动态绑定 (Dynamic Binding): 如前所述,当基类 (base class) 的析构函数是虚函数 (virtual function) 时,通过 基类 (base class) 指针调用析构函数会发生动态绑定 (dynamic binding)。这意味着实际调用的析构函数版本取决于对象的实际类型,而不是指针的静态类型。

    ▮▮▮▮ⓑ shared_ptr 存储类型信息: shared_ptr<T> 在内部会记录它所管理的对象的类型信息(实际上是通过控制块 (control block) 和删除器 (deleter) 实现的)。即使 shared_ptr 是用 基类 (base class) 指针类型创建的(例如 shared_ptr<Base>),它仍然能够记住它实际管理的是 派生类 (derived class) 对象。

    ▮▮▮▮ⓒ 删除器 (Deleter) 的使用: shared_ptr 在析构对象时,实际上是通过一个 删除器 (deleter) 来完成的。默认情况下,shared_ptr 使用 delete 运算符作为其删除器 (deleter)。对于多态对象,当 shared_ptr<Base> 管理 派生类 (derived class) 对象并需要删除对象时,shared_ptr 会通过其内部存储的删除器 (deleter) (即 delete) 来操作。由于 虚析构函数 (virtual destructors) 的存在,这个 delete 操作会触发多态行为,确保调用正确的析构函数链。

    结合以上机制,当 shared_ptr<Base> 管理 派生类 (derived class) 对象时,即使 shared_ptr 本身是 基类 (base class) 类型的,由于 Base 类析构函数是虚函数 (virtual function),当 shared_ptr 析构并调用删除器 (deleter) (即 delete) 时,会正确地先调用 派生类 (derived class) 的析构函数,然后再调用 基类 (base class) 的析构函数,从而实现多态删除 (polymorphic deletion)。

    代码示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class Base {
    5 public:
    6 Base() { std::cout << "Base constructor" << std::endl; }
    7 virtual ~Base() { std::cout << "Base destructor" << std::endl; } // 虚析构函数
    8 virtual void printType() const { std::cout << "Type: Base" << std::endl; }
    9 };
    10
    11 class Derived : public Base {
    12 public:
    13 Derived() { std::cout << "Derived constructor" << std::endl; }
    14 ~Derived() { std::cout << "Derived destructor" << std::endl; }
    15 void printType() const override { std::cout << "Type: Derived" << std::endl; }
    16 private:
    17 int* data; // 派生类特有的资源
    18 };
    19
    20 int main() {
    21 {
    22 std::shared_ptr<Base> polyPtr = std::make_shared<Derived>(); // shared_ptr<Base> 管理 Derived 对象
    23 polyPtr->printType(); // 调用派生类的 printType,展示多态行为 (polymorphism)
    24 } // polyPtr 超出作用域,shared_ptr 析构,触发多态删除 (polymorphic deletion)
    25
    26 return 0;
    27 }

    在这个例子中,Base 类有 虚析构函数 (virtual destructors) 和 虚函数 (virtual function) printType()Derived 类继承自 Base 并重写了 printType()。在 main 函数中,创建了一个 std::shared_ptr<Base> 来管理 Derived 对象。当 polyPtr 超出作用域时,shared_ptr 会自动析构,由于 Base 的析构函数是虚函数 (virtual function),会发生多态删除 (polymorphic deletion),确保 DerivedBase 的析构函数都被调用,从而安全地释放 Derived 对象的所有资源。

    多态删除 (Polymorphic Deletion) 的重要性:

    正确的多态删除 (polymorphic deletion) 是 C++ 多态 (polymorphism) 的基石,尤其是在使用继承和动态多态 (dynamic polymorphism) 时。如果多态对象没有被正确删除,可能会导致以下问题:

    ▮▮▮▮ⓐ 资源泄漏 (Resource Leaks): 如果 派生类 (derived class) 在析构函数中释放了特定的资源(例如动态分配的内存、打开的文件句柄等),而 派生类 (derived class) 的析构函数没有被调用,这些资源就可能无法被释放,导致资源泄漏。
    ▮▮▮▮ⓑ 程序状态错误: 在某些情况下,部分析构 (partial destruction) 可能会导致对象的状态不一致,从而引发程序运行时错误。
    ▮▮▮▮ⓒ 未定义行为: 更糟糕的是,部分析构 (partial destruction) 有时会导致未定义行为,使得程序行为不可预测,难以调试。

    通过使用 虚析构函数 (virtual destructors) 和 shared_ptr,可以有效地避免这些问题,确保多态对象被安全、完整地析构,从而提高程序的健壮性和可靠性。

    最佳实践:

    ▮▮▮▮ⓐ 始终为多态基类提供虚析构函数 (virtual destructors): 如果一个类打算作为基类 (base class) 使用,并且可能会通过 基类 (base class) 指针操作 派生类 (derived class) 对象,那么就应该为其提供 虚析构函数 (virtual destructors)。这是一个良好的设计原则,可以避免潜在的资源管理问题。
    ▮▮▮▮ⓑ 使用智能指针管理多态对象: 为了简化多态对象的生命周期管理并避免手动内存管理的错误,推荐使用智能指针(如 shared_ptr)来管理多态对象。shared_ptr 可以自动处理对象的删除,并与 虚析构函数 (virtual destructors) 协同工作,确保多态删除 (polymorphic deletion) 的正确性。
    ▮▮▮▮ⓒ 注意接口设计: 在设计类层次结构时,要仔细考虑接口和所有权模型。确保基类 (base class) 的接口能够支持派生类 (derived class) 的需求,并且所有权管理清晰明确。

    总结来说,shared_ptr 通过与 虚析构函数 (virtual destructors) 协同工作,为 C++ 中的多态对象提供了安全可靠的生命周期管理方案。理解多态删除 (polymorphic deletion) 的原理和重要性,并在实践中遵循最佳实践,是编写高质量、可维护的 C++ 代码的关键。

    4.3 工厂模式与 shared_ptr (Factory Pattern and shared_ptr)

    本节 demonstrates how shared_ptr can be used effectively in 工厂模式 (factory patterns) for object creation and management. (本节演示了如何在工厂模式中有效地使用 shared_ptr 进行对象创建和管理。)

    工厂模式 (factory pattern) 是一种创建型设计模式,它提供了一种将对象创建的责任委托给工厂类的机制。使用 工厂模式 (factory pattern) 可以提高代码的灵活性、可维护性和可扩展性。当与 shared_ptr 结合使用时,工厂模式 (factory pattern) 可以更好地管理创建对象的生命周期,并简化客户端代码的资源管理。

    4.3.1 工厂函数返回 shared_ptr (Factory Functions Returning shared_ptr)

    Shows how 工厂函数 (factory functions) can return shared_ptr to manage the lifetime of created objects and ensure proper 资源管理 (resource management). (展示了工厂函数如何返回 shared_ptr 以管理已创建对象的生命周期并确保正确的资源管理。)

    工厂模式 (Factory Pattern) 的基本概念:

    工厂模式 (factory pattern) 的核心思想是将对象的创建逻辑封装在一个或多个工厂类或工厂函数中,客户端代码通过工厂来请求对象,而无需直接使用 new 运算符创建对象。这样做的好处包括:

    ▮▮▮▮ⓐ 解耦 (Decoupling): 客户端代码与具体对象类型的创建逻辑解耦,客户端只需要知道工厂接口,而无需关心具体对象的创建细节。
    ▮▮▮▮ⓑ 封装创建逻辑: 复杂的对象创建逻辑可以封装在工厂内部,使得客户端代码更简洁。
    ▮▮▮▮ⓒ 灵活性和可扩展性: 可以更容易地替换或扩展对象的创建方式,例如添加新的对象类型、更改对象的配置等,而不会影响客户端代码。

    工厂模式 (factory pattern) 主要分为简单工厂模式 (Simple Factory Pattern)、工厂方法模式 (Factory Method Pattern) 和抽象工厂模式 (Abstract Factory Pattern) 等多种变体。在本节中,我们主要关注使用 工厂函数 (factory functions) 返回 shared_ptr 的方法,这是一种常见的简单工厂模式 (Simple Factory Pattern) 的应用。

    工厂函数 (Factory Functions) 返回 shared_ptr:

    工厂函数 (factory function) 是一个独立的函数,它负责创建对象并返回指向该对象的智能指针,通常是 shared_ptr。通过返回 shared_ptr,工厂函数 (factory function) 将对象的生命周期管理责任转移给了客户端代码。客户端代码接收到 shared_ptr 后,就可以像使用普通 shared_ptr 一样管理对象的生命周期,无需手动 delete

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <string>
    4
    5 class Product { // 抽象产品类
    6 public:
    7 virtual ~Product() = default;
    8 virtual void use() const = 0;
    9 };
    10
    11 class ConcreteProductA : public Product { // 具体产品类 A
    12 public:
    13 void use() const override {
    14 std::cout << "Using ConcreteProductA" << std::endl;
    15 }
    16 };
    17
    18 class ConcreteProductB : public Product { // 具体产品类 B
    19 public:
    20 void use() const override {
    21 std::cout << "Using ConcreteProductB" << std::endl;
    22 }
    23 };
    24
    25 // 工厂函数 (factory function)
    26 std::shared_ptr<Product> createProduct(char type) {
    27 if (type == 'A') {
    28 return std::make_shared<ConcreteProductA>();
    29 } else if (type == 'B') {
    30 return std::make_shared<ConcreteProductB>();
    31 } else {
    32 return nullptr; // 或者抛出异常,根据实际情况处理错误
    33 }
    34 }
    35
    36 int main() {
    37 std::shared_ptr<Product> productA = createProduct('A');
    38 if (productA) {
    39 productA->use(); // 使用产品 A
    40 }
    41
    42 std::shared_ptr<Product> productB = createProduct('B');
    43 if (productB) {
    44 productB->use(); // 使用产品 B
    45 }
    46
    47 // productA 和 productB 超出作用域后,它们指向的对象会被自动删除
    48 return 0;
    49 }

    在这个例子中,createProduct 是一个 工厂函数 (factory function),它根据输入的类型参数 type 创建不同的 Product 派生类对象,并返回指向这些对象的 std::shared_ptr。客户端代码通过调用 createProduct 获取 shared_ptr<Product>,然后就可以使用这些产品对象,而无需关心对象的具体创建过程和内存管理。当 productAproductB 超出作用域时,它们所管理的 ConcreteProductAConcreteProductB 对象会被自动删除。

    工厂函数 (Factory Functions) 返回 shared_ptr 的优势:

    ▮▮▮▮ⓐ 自动内存管理: 通过返回 shared_ptr,工厂函数 (factory function) 将创建对象的内存管理责任转移给了客户端代码。客户端代码无需手动 delete 对象,shared_ptr 会自动处理对象的生命周期,避免内存泄漏。
    ▮▮▮▮ⓑ 异常安全: 使用 std::make_shared 在工厂函数 (factory function) 中创建对象是异常安全的。如果在对象构造过程中抛出异常,make_shared 能够确保不会发生资源泄漏。
    ▮▮▮▮ⓒ 简化客户端代码: 客户端代码无需关心对象的创建细节和内存管理,只需要通过工厂函数 (factory function) 获取 shared_ptr,然后像使用普通对象一样使用即可,代码更简洁、清晰。
    ▮▮▮▮ⓓ 支持多态 (polymorphism): 工厂函数 (factory function) 可以返回指向抽象基类 (abstract base class) 的 shared_ptr,客户端代码可以通过基类 (base class) 接口操作各种具体产品对象,实现多态行为 (polymorphism)。
    ▮▮▮▮ⓔ 延迟创建 (Lazy Creation) 和对象池 (Object Pooling): 工厂模式 (factory pattern) 可以方便地实现延迟创建 (lazy creation) 和对象池 (object pooling) 等高级特性,进一步优化性能和资源利用率。

    工厂函数 (Factory Functions) 的设计考虑:

    ▮▮▮▮ⓐ 工厂函数的参数: 工厂函数的参数应该足够灵活,能够根据不同的需求创建不同类型的对象。可以使用枚举 (enum)、字符串 (string) 或其他类型来指定要创建的对象类型,还可以传递配置参数来定制对象的创建过程。
    ▮▮▮▮ⓑ 错误处理: 工厂函数 (factory function) 需要处理对象创建过程中可能出现的错误。例如,如果无法创建指定类型的对象,工厂函数 (factory function) 可以返回空指针 (nullptr),或者抛出异常,让客户端代码来处理错误情况。
    ▮▮▮▮ⓒ 命名约定: 工厂函数的命名应该清晰明了,能够表达其创建对象的意图。常见的命名方式包括 createProduct, makeObject, getInstance 等。

    示例:使用工厂函数 (Factory Functions) 创建不同类型的形状对象:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <string>
    4
    5 class Shape { // 抽象形状类
    6 public:
    7 virtual ~Shape() = default;
    8 virtual void draw() const = 0;
    9 };
    10
    11 class Circle : public Shape { // 具体形状类:圆形
    12 public:
    13 void draw() const override {
    14 std::cout << "Drawing Circle" << std::endl;
    15 }
    16 };
    17
    18 class Square : public Shape { // 具体形状类:正方形
    19 public:
    20 void draw() const override {
    21 std::cout << "Drawing Square" << std::endl;
    22 }
    23 };
    24
    25 // 工厂函数 (factory function) 创建形状对象
    26 std::shared_ptr<Shape> createShape(const std::string& type) {
    27 if (type == "circle") {
    28 return std::make_shared<Circle>();
    29 } else if (type == "square") {
    30 return std::make_shared<Square>();
    31 } else {
    32 return nullptr;
    33 }
    34 }
    35
    36 int main() {
    37 std::shared_ptr<Shape> circle = createShape("circle");
    38 if (circle) {
    39 circle->draw(); // 绘制圆形
    40 }
    41
    42 std::shared_ptr<Shape> square = createShape("square");
    43 if (square) {
    44 square->draw(); // 绘制正方形
    45 }
    46
    47 std::shared_ptr<Shape> triangle = createShape("triangle"); // 创建未知类型
    48 if (!triangle) {
    49 std::cout << "Unknown shape type: triangle" << std::endl;
    50 }
    51
    52 return 0;
    53 }

    在这个例子中,createShape 工厂函数 (factory function) 根据输入的形状类型字符串创建 CircleSquare 对象,并返回 shared_ptr<Shape>。客户端代码可以通过字符串参数请求不同类型的形状对象,工厂函数 (factory function) 负责对象的创建和生命周期管理。

    总结来说,工厂函数 (factory functions) 返回 shared_ptr 是一种在 C++ 中实现工厂模式 (factory pattern) 的有效方法。它结合了工厂模式 (factory pattern) 的灵活性和 shared_ptr 的自动内存管理优势,能够创建出更清晰、更安全、更易于维护的代码。通过合理地设计工厂函数 (factory function),可以简化对象的创建和管理,并提高代码的可扩展性和可测试性。

    4.3.2 简化对象生命周期管理 (Simplifying Object Lifetime Management)

    Explains how using shared_ptr in 工厂模式 (factory patterns) simplifies 对象生命周期管理 (object lifetime management) and reduces the risk of 内存泄漏 (memory leaks). (解释了在工厂模式中使用 shared_ptr 如何简化对象生命周期管理并降低内存泄漏的风险。)

    使用 shared_ptr 在 工厂模式 (factory pattern) 中最显著的优势之一就是简化了 对象生命周期管理 (object lifetime management)。传统的对象创建和管理方式,尤其是使用 newdelete 手动管理内存时,容易出现内存泄漏 (memory leaks) 和悬挂指针 (dangling pointers) 等问题。而 工厂函数 (factory functions) 返回 shared_ptr 的方法,能够极大地简化资源管理,并降低这些风险。

    手动内存管理的挑战:

    在不使用智能指针的情况下,客户端代码需要负责手动管理通过工厂创建的对象的生命周期。这意味着:

    ▮▮▮▮ⓐ 谁负责 delete: 客户端代码必须明确知道何时以及如何 delete 工厂创建的对象。如果对象的所有权和生命周期管理责任不明确,很容易发生忘记 delete 导致内存泄漏 (memory leaks),或者重复 delete 导致程序崩溃。
    ▮▮▮▮ⓑ 异常安全问题: 如果在对象创建后和 delete 之前,程序抛出异常,delete 操作可能不会被执行,从而导致资源泄漏。为了保证异常安全,需要编写复杂的异常处理代码,例如使用 try-catch 块,并在 catch 块中进行清理操作。
    ▮▮▮▮ⓒ 复杂的所有权管理: 当多个组件或模块需要共享工厂创建的对象时,手动管理对象的生命周期变得非常复杂。需要仔细跟踪对象的所有权,确保在所有使用者都完成使用后才 delete 对象。

    shared_ptr 简化生命周期管理:

    当 工厂函数 (factory functions) 返回 shared_ptr 时,对象的生命周期管理变得自动化和安全:

    ▮▮▮▮ⓐ 自动内存回收: 客户端代码接收到 shared_ptr 后,就可以像使用普通变量一样使用它,无需显式地 deleteshared_ptr 使用引用计数 (reference counting) 机制跟踪对象的引用数量。当最后一个指向对象的 shared_ptr 实例被销毁时(例如超出作用域),对象所占用的内存会被自动释放,无需手动干预。
    ▮▮▮▮ⓑ 异常安全: shared_ptr 的自动内存管理是基于 RAII (Resource Acquisition Is Initialization) 原则实现的。即使在程序运行过程中抛出异常,shared_ptr 的析构函数仍然会被调用,确保资源被正确释放,从而避免内存泄漏,实现异常安全。
    ▮▮▮▮ⓒ 共享所有权: shared_ptr 支持共享所有权。多个客户端代码可以持有指向同一个对象的 shared_ptr,共享对象的所有权。对象会在所有 shared_ptr 都销毁后才被删除,这使得在复杂系统中管理对象生命周期变得更加容易。

    代码示例对比:手动管理 vs. shared_ptr 管理:

    手动管理 (容易出错):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2
    3 class MyObject {
    4 public:
    5 MyObject(int id) : id_(id) {
    6 std::cout << "MyObject " << id_ << " created." << std::endl;
    7 }
    8 ~MyObject() {
    9 std::cout << "MyObject " << id_ << " destroyed." << std::endl;
    10 }
    11 private:
    12 int id_;
    13 };
    14
    15 MyObject* createMyObject(int id) {
    16 return new MyObject(id); // 工厂函数返回裸指针
    17 }
    18
    19 void processObject() {
    20 MyObject* obj = createMyObject(1);
    21 // ... 使用 obj ...
    22 // 容易忘记 delete obj,导致内存泄漏
    23 // delete obj; // 需要手动 delete
    24 } // 如果这里没有 delete obj,则发生内存泄漏
    25
    26 int main() {
    27 processObject();
    28 return 0;
    29 }

    shared_ptr 管理 (安全且简洁):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class MyObject {
    5 public:
    6 MyObject(int id) : id_(id) {
    7 std::cout << "MyObject " << id_ << " created." << std::endl;
    8 }
    9 ~MyObject() {
    10 std::cout << "MyObject " << id_ << " destroyed." << std::endl;
    11 }
    12 private:
    13 int id_;
    14 };
    15
    16 std::shared_ptr<MyObject> createMyObject(int id) {
    17 return std::make_shared<MyObject>(id); // 工厂函数返回 shared_ptr
    18 }
    19
    20 void processObject() {
    21 std::shared_ptr<MyObject> obj = createMyObject(2);
    22 // ... 使用 obj ...
    23 // 无需手动 delete,shared_ptr 自动管理生命周期
    24 } // obj 超出作用域,shared_ptr 析构,对象自动删除
    25
    26 int main() {
    27 processObject();
    28 return 0;
    29 }

    在第二个使用 shared_ptr 的例子中,客户端代码 processObject 函数无需显式地管理对象的内存。当 obj shared_ptr 超出作用域时,它会自动释放所管理的 MyObject 对象的内存。这大大简化了代码,并消除了手动内存管理可能引入的错误。

    最佳实践:

    ▮▮▮▮ⓐ 优先使用 shared_ptr 作为工厂函数的返回值: 在设计工厂模式 (factory pattern) 时,优先考虑让工厂函数 (factory functions) 返回 shared_ptr,以便将对象生命周期管理责任转移给客户端代码,并简化资源管理。
    ▮▮▮▮ⓑ 避免返回裸指针: 尽量避免工厂函数 (factory functions) 返回裸指针,除非有非常特殊的原因(例如需要与旧代码库兼容,或者性能至关重要且能确保手动管理的正确性)。返回裸指针会增加客户端代码的复杂性和出错风险。
    ▮▮▮▮ⓒ 明确所有权模型: 在使用工厂模式 (factory pattern) 和 shared_ptr 时,需要明确对象的所有权模型。如果对象需要被多个组件共享,shared_ptr 是一个很好的选择。如果对象的所有权是独占的,可以考虑使用 unique_ptr
    ▮▮▮▮ⓓ 结合 make_shared: 在工厂函数 (factory functions) 内部,使用 std::make_shared 创建 shared_ptr,以提高效率和异常安全性。

    高级应用:对象池 (Object Pooling) 与 工厂模式 (Factory Pattern):

    工厂模式 (factory pattern) 和 shared_ptr 还可以结合用于实现对象池 (object pooling)。对象池 (object pooling) 是一种性能优化技术,它预先创建一组对象,并将它们存储在一个池中。当需要使用对象时,从池中获取一个,使用完毕后返回池中,而不是每次都创建和销毁对象,从而减少对象创建和销毁的开销。使用 工厂模式 (factory pattern) 可以负责创建和管理对象池中的对象,而 shared_ptr 可以用于管理池中对象的生命周期和共享。

    总结来说,通过在 工厂模式 (factory pattern) 中使用 shared_ptr,可以极大地简化 对象生命周期管理 (object lifetime management),降低内存泄漏 (memory leaks) 的风险,并提高代码的健壮性和可维护性。工厂函数 (factory functions) 返回 shared_ptr 是一种现代 C++ 编程中推荐的最佳实践,特别是在需要创建和管理复杂对象关系和生命周期的系统中。

    4.4 Pimpl 惯用法与 shared_ptr (Pimpl Idiom and shared_ptr)

    本节 explains how shared_ptr can be used to implement the Pimpl (Pointer to implementation) 惯用法 (idiom) for better 封装 (encapsulation) and 编译时解耦 (compile-time decoupling). (本节解释了如何使用 shared_ptr 来实现 Pimpl(指向实现的指针)惯用法,以实现更好的封装和编译时解耦。)

    Pimpl 惯用法 (Pimpl idiom),也称为 编译防火墙 (Compilation Firewall)Handle-Body Idiom,是一种常用的 C++ 编程技术,用于提高 封装 (encapsulation)、减少编译依赖和加快编译速度。shared_ptr 在实现 Pimpl 惯用法 (Pimpl idiom) 中扮演着重要角色,它可以安全地管理实现指针的生命周期。

    4.4.1 使用 shared_ptr 实现 Pimpl (Implementing Pimpl with shared_ptr)

    Demonstrates how to use shared_ptr to manage the 实现指针 (implementation pointer) in the Pimpl 惯用法 (idiom). (演示了如何在 Pimpl 惯用法中使用 shared_ptr 来管理实现指针。)

    Pimpl 惯用法 (Pimpl Idiom) 的基本思想:

    Pimpl 惯用法 (Pimpl idiom) 的核心思想是将类的实现细节(包括私有成员变量和私有方法)从类的头文件 (header file) 中分离出来,放到一个单独的实现类中,并通过一个指针在原始类中指向这个实现类。原始类只包含公有接口和指向实现类的指针。这样做的好处主要有:

    ▮▮▮▮ⓐ 增强封装 (Encapsulation): 客户端代码只能看到类的公有接口,完全隐藏了实现细节,包括私有成员变量和私有方法的具体实现。这提高了代码的 封装性 (encapsulation) 和安全性。
    ▮▮▮▮ⓑ 减少编译依赖 (Compile-time Decoupling): 当实现类的头文件 (header file) 发生变化时(例如修改了私有成员变量),只需要重新编译实现类的源文件 (source file) 和原始类的源文件 (source file),而无需重新编译使用原始类头文件 (header file) 的客户端代码。这显著减少了编译依赖,加快了编译速度,尤其是在大型项目中。
    ▮▮▮▮ⓒ 二进制兼容性 (Binary Compatibility): 在某些情况下,Pimpl 惯用法 (Pimpl idiom) 可以提高二进制兼容性。如果只修改了实现类的私有部分,而公有接口保持不变,那么在某些平台上可以实现二进制兼容,即已编译的客户端代码无需重新编译即可与修改后的库链接。

    使用 shared_ptr 实现 Pimpl 惯用法 (Pimpl Idiom with shared_ptr):

    shared_ptr 非常适合用于管理 Pimpl 惯用法 (Pimpl idiom) 中的实现指针。通过使用 shared_ptr,可以自动管理实现类的生命周期,避免手动 newdelete,并确保异常安全。

    示例代码:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // MyClass.h (头文件)
    2 #ifndef MYCLASS_H
    3 #define MYCLASS_H
    4
    5 #include <memory>
    6 #include <string>
    7
    8 class MyClass {
    9 public:
    10 MyClass();
    11 ~MyClass();
    12
    13 void setValue(int value);
    14 int getValue() const;
    15 void printName() const;
    16
    17 private:
    18 class Impl; // 前向声明实现类 (forward declaration of implementation class)
    19 std::shared_ptr<Impl> pimpl_; // 使用 shared_ptr 管理实现指针
    20 };
    21
    22 #endif // MYCLASS_H
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // MyClass.cpp (源文件)
    2 #include "MyClass.h"
    3 #include <iostream>
    4
    5 // 实现类 (Implementation Class)
    6 class MyClass::Impl {
    7 public:
    8 Impl() : value_(0), name_("Default Name") {}
    9 int value_;
    10 std::string name_;
    11 };
    12
    13 // 原始类 (Original Class) 的实现
    14 MyClass::MyClass() : pimpl_(std::make_shared<Impl>()) {}
    15
    16 MyClass::~MyClass() = default; // 使用默认析构函数,shared_ptr 会自动析构
    17
    18 void MyClass::setValue(int value) {
    19 pimpl_->value_ = value;
    20 }
    21
    22 int MyClass::getValue() const {
    23 return pimpl_->value_;
    24 }
    25
    26 void MyClass::printName() const {
    27 std::cout << "Name: " << pimpl_->name_ << std::endl;
    28 }

    代码解释:

    ▮▮▮▮ⓐ 头文件 (MyClass.h):
    ▮▮▮▮▮▮▮▮⚝ MyClass 类只声明了公有接口和私有的实现指针 pimpl_,类型为 std::shared_ptr<Impl>
    ▮▮▮▮▮▮▮▮⚝ Impl 类是一个私有的嵌套类,只进行了前向声明 (forward declaration),其完整定义放在源文件 (MyClass.cpp) 中。
    ▮▮▮▮▮▮▮▮⚝ 客户端代码只能看到 MyClass.h,无法直接访问 Impl 类的定义,从而实现了 封装 (encapsulation)。

    ▮▮▮▮ⓑ 源文件 (MyClass.cpp):
    ▮▮▮▮▮▮▮▮⚝ Impl 类的完整定义在 MyClass.cpp 中,包括私有成员变量 value_name_
    ▮▮▮▮▮▮▮▮⚝ MyClass 的构造函数使用 std::make_shared<Impl>() 创建 Impl 类的实例,并将返回的 shared_ptr 赋值给 pimpl_
    ▮▮▮▮▮▮▮▮⚝ MyClass 的析构函数使用默认实现 = default。由于 pimpl_shared_ptr,当 MyClass 对象销毁时,pimpl_ 会自动析构,并释放 Impl 对象的内存。
    ▮▮▮▮▮▮▮▮⚝ MyClass 的公有方法(如 setValue, getValue, printName)通过 pimpl_ 指针访问 Impl 类的成员变量。

    通过这种方式,MyClass 的实现细节完全隐藏在 MyClass.cpp 中,对客户端代码不可见。当 Impl 类的实现细节发生变化时,只需要重新编译 MyClass.cpp,而无需重新编译客户端代码,实现了编译时解耦 (compile-time decoupling)。

    Pimpl 惯用法 (Pimpl Idiom) 的优势:

    ▮▮▮▮ⓐ 增强封装 (Encapsulation): 完全隐藏实现细节,只暴露公有接口,提高了代码的 封装性 (encapsulation) 和模块化程度。
    ▮▮▮▮ⓑ 减少编译依赖 (Compile-time Decoupling): 修改实现细节不会导致客户端代码重新编译,加快了编译速度,尤其是在大型项目中效果显著。
    ▮▮▮▮ⓒ 提高二进制兼容性 (Binary Compatibility): 在一定程度上提高了二进制兼容性,允许在不破坏二进制兼容性的前提下修改实现细节。
    ▮▮▮▮ⓓ 简化头文件 (Header File): 头文件 (header file) 更加简洁,只包含公有接口声明,提高了头文件 (header file) 的可读性和可维护性。
    ▮▮▮▮ⓔ 异常安全和自动内存管理: 使用 shared_ptr 管理实现指针,实现了自动内存管理,避免了手动 newdelete,并保证了异常安全。

    Pimpl 惯用法 (Pimpl Idiom) 的注意事项:

    ▮▮▮▮ⓐ 增加间接访问的开销: 每次访问实现类的成员变量都需要通过指针间接访问,可能会带来一定的性能开销。但在大多数应用场景下,这种开销通常可以忽略不计。
    ▮▮▮▮ⓑ 实现类需要完整定义: 实现类 (Impl) 的完整定义必须放在源文件 (source file) 中,不能只进行前向声明 (forward declaration)。
    ▮▮▮▮ⓒ 复制和赋值语义 (Copy and Assignment Semantics): 需要仔细考虑原始类的复制构造函数 (copy constructor)、移动构造函数 (move constructor)、复制赋值运算符 (copy assignment operator) 和移动赋值运算符 (move assignment operator)。通常可以使用默认实现,或者根据需要自定义实现。对于使用 shared_ptr 管理实现指针的情况,默认的复制和赋值操作通常是安全的。
    ▮▮▮▮ⓓ 调试复杂性: 在调试时,可能需要深入到实现类中才能查看具体的实现细节,可能会增加一定的调试复杂性。

    总结:

    Pimpl 惯用法 (Pimpl idiom) 是一种强大的 C++ 编程技术,用于提高 封装 (encapsulation)、减少编译依赖和加快编译速度。shared_ptr 是实现 Pimpl 惯用法 (Pimpl idiom) 的理想工具,它可以安全、高效地管理实现指针的生命周期。通过结合 Pimpl 惯用法 (Pimpl idiom) 和 shared_ptr,可以编写出更健壮、更灵活、更易于维护的 C++ 代码,特别是在大型项目和库的设计中,Pimpl 惯用法 (Pimpl idiom) 是一种非常有价值的设计模式。

    5. shared_ptr 与 weak_ptr、unique_ptr 的比较 (Comparison of shared_ptr with weak_ptr and unique_ptr)

    本章将 shared_ptr 与其他智能指针,即 weak_ptrunique_ptr,进行全面的比较。我们将重点关注它们各自的角色、典型的使用场景,以及在不同情况下如何选择最合适的智能指针类型。理解这些智能指针之间的差异和适用性,对于编写安全、高效且易于维护的现代 C++ 代码至关重要。 (This chapter comprehensively compares shared_ptr with other smart pointers, namely weak_ptr and unique_ptr. We will focus on their respective roles, typical use cases, and how to choose the most suitable smart pointer type in different situations. Understanding the differences and applicability of these smart pointers is crucial for writing safe, efficient, and maintainable modern C++ code.)

    5.1 weak_ptr 的作用与使用场景 (Role and Use Cases of weak_ptr)

    weak_ptr 是一种特殊的智能指针,它不参与到共享所有权中。weak_ptr 被设计用来观察 shared_ptr 所管理的对象,而不会增加对象的引用计数。这使得 weak_ptr 在解决循环引用问题和实现观察者模式等方面非常有用。 (A weak_ptr is a special type of smart pointer that does not participate in shared ownership. weak_ptr is designed to observe objects managed by shared_ptr without increasing the object's reference count. This makes weak_ptr very useful in resolving circular reference issues and implementing observer patterns, among other scenarios.)

    5.1.1 解决循环引用 (Resolving Circular Dependencies)

    循环引用 (Circular Reference) 是 shared_ptr 使用中一个经典且需要谨慎处理的问题。当两个或多个对象彼此持有 shared_ptr 指向对方时,即使这些对象已经不再被程序的其他部分使用,它们的引用计数也永远不会降为零,从而导致内存泄漏 (Memory Leak)。

    例如,考虑两个类 AB,它们各自有一个 shared_ptr 指向对方的实例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class B; // Forward declaration
    5
    6 class A {
    7 public:
    8 std::shared_ptr<B> b_ptr;
    9 ~A() { std::cout << "A destructor called" << std::endl; }
    10 };
    11
    12 class B {
    13 public:
    14 std::shared_ptr<A> a_ptr;
    15 ~B() { std::cout << "B destructor called" << std::endl; }
    16 };
    17
    18 int main() {
    19 std::shared_ptr<A> a = std::make_shared<A>();
    20 std::shared_ptr<B> b = std::make_shared<B>();
    21 a->b_ptr = b;
    22 b->a_ptr = a;
    23 return 0;
    24 }

    在这个例子中,ab 互相持有 shared_ptr,形成循环引用。当 main 函数结束时,ab 超出作用域,但由于循环引用,AB 对象的引用计数都为 1,析构函数永远不会被调用,导致内存泄漏。

    weak_ptr 可以有效地解决这个问题。我们只需要将循环引用中的一个 shared_ptr 改为 weak_ptr 即可打破循环。通常,将“从属”关系一方的 shared_ptr 改为 weak_ptr 是一个好的策略。在上述例子中,假设类 B 的生命周期从属于类 A,我们可以将类 B 中的 std::shared_ptr<A> a_ptr; 改为 std::weak_ptr<A> a_ptr;

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class B; // Forward declaration
    5
    6 class A {
    7 public:
    8 std::shared_ptr<B> b_ptr;
    9 ~A() { std::cout << "A destructor called" << std::endl; }
    10 };
    11
    12 class B {
    13 public:
    14 std::weak_ptr<A> a_ptr; // Changed to weak_ptr
    15 ~B() { std::cout << "B destructor called" << std::endl; }
    16 };
    17
    18 int main() {
    19 std::shared_ptr<A> a = std::make_shared<A>();
    20 std::shared_ptr<B> b = std::make_shared<B>();
    21 a->b_ptr = b;
    22 b->a_ptr = a;
    23 return 0;
    24 }

    现在,当 main 函数结束时,ab 超出作用域,B 对象不再持有 A 对象的强引用,A 对象的引用计数降为 0,A 的析构函数被调用,进而 B 对象的引用计数也降为 0,B 的析构函数也被调用。循环引用被成功打破,避免了内存泄漏。

    5.1.2 观察者模式 (Observer Pattern)

    在观察者模式 (Observer Pattern) 中,观察者 (Observer) 需要观察 主题 (Subject) 的状态变化。通常,主题会维护一个观察者列表,并在状态变化时通知所有观察者。然而,如果主题使用 shared_ptr 来管理观察者,可能会导致观察者对象无法被及时销毁,特别是当观察者对象本身也持有主题的引用时。

    使用 weak_ptr 可以让观察者在不影响主题生命周期的情况下观察主题。观察者可以使用 weak_ptr 来引用主题,并在需要时尝试通过 lock() 方法将其转换为 shared_ptr 来访问主题对象。如果主题对象仍然存活,lock() 方法会返回一个有效的 shared_ptr;否则,返回空的 shared_ptr,表明主题对象已经被销毁。

    以下是一个简单的观察者模式的示例,使用了 weak_ptr 来避免循环引用和悬挂指针 (Dangling Pointer) 的问题:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3 #include <vector>
    4
    5 class Observer; // Forward declaration
    6
    7 class Subject {
    8 public:
    9 void attach(std::shared_ptr<Observer> observer) {
    10 observers_.push_back(observer);
    11 }
    12
    13 void detach(std::shared_ptr<Observer> observer) {
    14 // Implementation to remove observer from the list
    15 for (auto it = observers_.begin(); it != observers_.end(); ++it) {
    16 if (*it == observer) {
    17 observers_.erase(it);
    18 break;
    19 }
    20 }
    21 }
    22
    23 void notify() {
    24 for (const auto& observer_ptr : observers_) {
    25 observer_ptr->update(this);
    26 }
    27 }
    28
    29 void setState(int state) {
    30 state_ = state;
    31 notify();
    32 }
    33
    34 int getState() const {
    35 return state_;
    36 }
    37
    38 private:
    39 std::vector<std::shared_ptr<Observer>> observers_;
    40 int state_;
    41 };
    42
    43 class Observer {
    44 public:
    45 virtual ~Observer() = default;
    46 virtual void update(Subject* subject) = 0;
    47 };
    48
    49 class ConcreteObserver : public Observer {
    50 public:
    51 ConcreteObserver(std::weak_ptr<Subject> subject) : subject_(subject) {}
    52
    53 void update(Subject* /*subject*/) override {
    54 if (auto subject_ptr = subject_.lock()) { // Attempt to lock weak_ptr to get shared_ptr
    55 std::cout << "ConcreteObserver: Subject's state is: " << subject_ptr->getState() << std::endl;
    56 } else {
    57 std::cout << "ConcreteObserver: Subject is no longer available." << std::endl;
    58 }
    59 }
    60
    61 private:
    62 std::weak_ptr<Subject> subject_; // Using weak_ptr to observe Subject
    63 };
    64
    65 int main() {
    66 auto subject = std::make_shared<Subject>();
    67 auto observer1 = std::make_shared<ConcreteObserver>(subject);
    68 auto observer2 = std::make_shared<ConcreteObserver>(subject);
    69
    70 subject->attach(observer1);
    71 subject->attach(observer2);
    72
    73 subject->setState(1);
    74 subject->setState(2);
    75
    76 subject->detach(observer2);
    77 subject->setState(3);
    78
    79 return 0;
    80 }

    在这个观察者模式的例子中,ConcreteObserver 使用 std::weak_ptr<Subject> subject_; 来观察 Subject 对象。当 Subject 的状态改变并通知观察者时,ConcreteObserver::update() 方法首先尝试使用 subject_.lock() 获取一个 shared_ptr<Subject>。如果 Subject 对象仍然存活,lock() 方法会成功返回一个有效的 shared_ptr,观察者可以安全地访问 Subject 的状态。如果 Subject 对象已经被销毁,lock() 方法会返回一个空的 shared_ptr,观察者可以据此判断 Subject 不再可用,从而避免访问悬挂指针。

    5.1.3 weak_ptr 的使用方法 (How to Use weak_ptr)

    weak_ptr 本身不能直接访问所指向的对象,它必须先转换为 shared_ptr 才能使用。weak_ptr 主要通过以下几种方式使用:

    shared_ptrweak_ptr 构造 (Construction from shared_ptr or weak_ptr): weak_ptr 可以从 shared_ptr 或另一个 weak_ptr 构造。这不会增加对象的引用计数。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<int> sp = std::make_shared<int>(42);
    2 std::weak_ptr<int> wp1 = sp; // Construct weak_ptr from shared_ptr
    3 std::weak_ptr<int> wp2 = wp1; // Construct weak_ptr from another weak_ptr

    lock() 方法 (The lock() method): lock() 方法是 weak_ptr 最重要的成员函数。它尝试将 weak_ptr 转换为 shared_ptr。如果 weak_ptr 指向的对象仍然存活(即引用计数大于 0),lock() 方法会返回一个新的 shared_ptr,指向同一个对象;如果对象已经被销毁(即引用计数为 0),lock() 方法会返回一个空的 shared_ptr

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::weak_ptr<int> wp;
    2 {
    3 std::shared_ptr<int> sp = std::make_shared<int>(100);
    4 wp = sp;
    5 if (auto locked_sp = wp.lock()) { // Use auto and if to check for null shared_ptr
    6 std::cout << "Value: " << *locked_sp << std::endl; // Access object through shared_ptr
    7 } else {
    8 std::cout << "Object expired!" << std::endl;
    9 }
    10 } // sp goes out of scope here, object may be destroyed
    11
    12 if (auto locked_sp = wp.lock()) {
    13 std::cout << "Value: " << *locked_sp << std::endl;
    14 } else {
    15 std::cout << "Object expired!" << std::endl; // Object expired after shared_ptr sp went out of scope
    16 }

    expired() 方法 (The expired() method): expired() 方法检查 weak_ptr 所指向的对象是否已经被销毁。它返回 true 如果对象已经被销毁,否则返回 false。 虽然 expired() 可以用来检查对象是否过期,但更推荐使用 lock() 方法,因为它在检查的同时还能安全地获取一个 shared_ptr,避免了在检查和使用之间对象可能被销毁的竞态条件 (Race Condition)。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::weak_ptr<int> wp;
    2 {
    3 std::shared_ptr<int> sp = std::make_shared<int>(200);
    4 wp = sp;
    5 if (!wp.expired()) {
    6 std::cout << "Object is still alive." << std::endl;
    7 } else {
    8 std::cout << "Object is expired." << std::endl;
    9 }
    10 } // sp goes out of scope here, object may be destroyed
    11
    12 if (wp.expired()) {
    13 std::cout << "Object is expired." << std::endl; // Object expired after shared_ptr sp went out of scope
    14 } else {
    15 std::cout << "Object is still alive." << std::endl; // This won't be reached in this example
    16 }

    reset() 方法 (The reset() method): reset() 方法使 weak_ptr 不再指向任何对象。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::weak_ptr<int> wp;
    2 {
    3 std::shared_ptr<int> sp = std::make_shared<int>(300);
    4 wp = sp;
    5 std::cout << "Weak pointer is pointing to an object." << std::endl;
    6 wp.reset(); // Reset weak_ptr, it no longer points to the object
    7 std::cout << "Weak pointer is reset." << std::endl;
    8 }
    9
    10 if (wp.expired()) {
    11 std::cout << "Weak pointer is expired after reset." << std::endl; // Weak pointer is expired after reset
    12 }

    use_count() 方法 (The use_count() method): use_count() 方法返回与 weak_ptr 关联的 shared_ptr 所共享的对象的所有权数量(即引用计数)。但需要注意的是,use_count() 的返回值可能瞬间就变得不准确,因为它返回的值仅仅是一个快照。因此,通常不建议依赖 use_count() 的返回值来进行程序逻辑的判断。

    5.2 unique_ptr 的作用与使用场景 (Role and Use Cases of unique_ptr)

    unique_ptr 实现了独占所有权 (Exclusive Ownership) 的语义。这意味着一个 unique_ptr 对象独占地拥有它所指向的对象。同一时间,不能有两个 unique_ptr 指向同一个对象。当 unique_ptr 对象被销毁时,它所指向的对象也会被自动销毁。unique_ptr 非常轻量级,其大小通常与裸指针 (Raw Pointer) 相同,并且没有 shared_ptr 那样的引用计数开销。 ( unique_ptr implements the semantics of exclusive ownership. This means that a unique_ptr object exclusively owns the object it points to. At any given time, no two unique_ptr objects can point to the same object. When a unique_ptr object is destroyed, the object it points to is also automatically destroyed. unique_ptr is very lightweight, its size is usually the same as a raw pointer, and it does not have the reference counting overhead of shared_ptr.)

    5.2.1 独占所有权 (Exclusive Ownership)

    unique_ptr 的核心特性是独占所有权。这种独占性通过以下方式体现:

    不可复制,但可移动 (Non-copyable but movable): unique_ptr 的拷贝构造函数 (Copy Constructor) 和拷贝赋值运算符 (Copy Assignment Operator) 被删除,这意味着你不能通过拷贝来创建新的 unique_ptr 对象。这确保了同一时间只有一个 unique_ptr 拥有特定对象的所有权。但是,unique_ptr 支持移动构造 (Move Construction)移动赋值 (Move Assignment)。通过移动操作,所有权可以从一个 unique_ptr 对象转移到另一个 unique_ptr 对象。移动后,原 unique_ptr 对象将不再拥有任何对象,变为空指针。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 int main() {
    5 std::unique_ptr<int> ptr1 = std::make_unique<int>(500);
    6 // std::unique_ptr<int> ptr2 = ptr1; // Error: Copy constructor is deleted
    7 std::unique_ptr<int> ptr2 = std::move(ptr1); // OK: Move ownership from ptr1 to ptr2
    8
    9 if (ptr1) {
    10 std::cout << "ptr1 still owns an object." << std::endl; // This won't be printed
    11 } else {
    12 std::cout << "ptr1 is now empty." << std::endl; // ptr1 is now empty
    13 }
    14
    15 if (ptr2) {
    16 std::cout << "ptr2 owns the object, value: " << *ptr2 << std::endl; // ptr2 owns the object
    17 }
    18
    19 return 0;
    20 }

    析构时自动删除所指对象 (Automatic deletion upon destruction): 当 unique_ptr 对象超出作用域或被显式销毁时,如果它拥有对象的所有权,它会自动调用 delete 运算符来释放所管理的内存。这确保了资源被及时回收,避免了内存泄漏。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class MyClass {
    5 public:
    6 MyClass(int value) : value_(value) {
    7 std::cout << "MyClass constructor called, value: " << value_ << std::endl;
    8 }
    9 ~MyClass() {
    10 std::cout << "MyClass destructor called, value: " << value_ << std::endl;
    11 }
    12 int getValue() const { return value_; }
    13 private:
    14 int value_;
    15 };
    16
    17 int main() {
    18 {
    19 std::unique_ptr<MyClass> myPtr = std::make_unique<MyClass>(10);
    20 std::cout << "Inside scope, value: " << myPtr->getValue() << std::endl;
    21 } // myPtr goes out of scope here, MyClass destructor will be called
    22
    23 std::cout << "Outside scope." << std::endl;
    24 return 0;
    25 }

    5.2.2 移动语义与 unique_ptr (Move Semantics and unique_ptr)

    由于 unique_ptr 不支持拷贝,移动语义 (Move Semantics) 成为了转移 unique_ptr 所有权的关键机制。移动语义允许资源的所有权从一个对象转移到另一个对象,而无需进行深拷贝 (Deep Copy)。这对于管理动态分配的资源非常高效,特别是当资源本身不可拷贝时。

    unique_ptr 的移动操作主要体现在移动构造函数和移动赋值运算符上。

    移动构造函数 (Move Constructor): 移动构造函数允许使用 std::move() 函数将一个 unique_ptr 对象的所有权转移给新的 unique_ptr 对象。移动后,原 unique_ptr 对象变为空。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::unique_ptr<int> createUniquePtr(int value) {
    2 return std::make_unique<int>(value); // Return by value, move constructor is called
    3 }
    4
    5 int main() {
    6 std::unique_ptr<int> ptr1;
    7 {
    8 std::unique_ptr<int> tempPtr = createUniquePtr(600);
    9 ptr1 = std::move(tempPtr); // Move ownership from tempPtr to ptr1
    10 // tempPtr is now empty
    11 } // tempPtr goes out of scope, but no object is deleted as ownership is moved to ptr1
    12
    13 if (ptr1) {
    14 std::cout << "ptr1 owns the object, value: " << *ptr1 << std::endl; // ptr1 owns the object
    15 }
    16
    17 return 0;
    18 }

    移动赋值运算符 (Move Assignment Operator): 移动赋值运算符允许将一个 unique_ptr 对象的所有权移动赋值给另一个已存在的 unique_ptr 对象。如果目标 unique_ptr 原本就拥有对象,移动赋值会先释放原来对象的所有权,然后转移新对象的所有权。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 int main() {
    5 std::unique_ptr<int> ptr1 = std::make_unique<int>(700);
    6 std::unique_ptr<int> ptr2 = std::make_unique<int>(800);
    7
    8 std::cout << "Before move assignment:" << std::endl;
    9 std::cout << "ptr1 value: " << *ptr1 << std::endl;
    10 std::cout << "ptr2 value: " << *ptr2 << std::endl;
    11
    12 ptr1 = std::move(ptr2); // Move ownership from ptr2 to ptr1
    13
    14 std::cout << "\nAfter move assignment:" << std::endl;
    15 std::cout << "ptr1 value: " << *ptr1 << std::endl; // ptr1 now owns the object originally owned by ptr2
    16 if (ptr2) {
    17 std::cout << "ptr2 still owns an object." << std::endl; // This won't be printed
    18 } else {
    19 std::cout << "ptr2 is now empty." << std::endl; // ptr2 is now empty
    20 }
    21 // The object originally owned by ptr1 (value 700) is now deleted
    22
    23 return 0;
    24 }

    5.2.3 unique_ptr 的使用方法 (How to Use unique_ptr)

    unique_ptr 的使用相对简单直接,主要包括以下几个方面:

    创建 unique_ptr (Creating unique_ptr): 通常使用 std::make_unique<T>(...) 函数来创建 unique_ptr,这是推荐的方式,因为它既高效又异常安全 (Exception Safety)。也可以直接使用 unique_ptr 的构造函数,但需要谨慎处理裸指针。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // Recommended way: using make_unique
    2 std::unique_ptr<int> ptr1 = std::make_unique<int>(900);
    3
    4 // Direct constructor (less recommended, need to handle raw pointer carefully)
    5 int* rawPtr = new int(1000);
    6 std::unique_ptr<int> ptr2(rawPtr); // Ownership transferred to unique_ptr
    7 // rawPtr is no longer responsible for memory management
    8
    9 // 주의: never do this, potential memory leak if exception occurs between new and unique_ptr constructor
    10 // std::unique_ptr<int> ptr3(new int(1100)); // If exception occurs after 'new int(1100)' but before unique_ptr construction, memory leak happens

    访问所指对象 (Accessing the managed object): 可以使用解引用操作符 * 和箭头操作符 -> 来访问 unique_ptr 所指向的对象,与裸指针类似。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::unique_ptr<std::string> strPtr = std::make_unique<std::string>("Hello unique_ptr");
    2 std::cout << *strPtr << std::endl; // Using dereference operator
    3 std::cout << strPtr->length() << std::endl; // Using arrow operator

    移动 unique_ptr (Moving unique_ptr): 使用 std::move() 函数来转移 unique_ptr 的所有权。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::unique_ptr<int> ptr1 = std::make_unique<int>(1200);
    2 std::unique_ptr<int> ptr2 = std::move(ptr1); // Move ownership from ptr1 to ptr2

    释放所指对象 (Releasing the managed object): 可以使用 release() 方法来释放 unique_ptr 对对象的所有权,并返回裸指针。释放所有权后,unique_ptr 变为空指针,但不会删除原来指向的对象。调用者需要负责管理返回的裸指针的生命周期。通常,除非有特殊需求,否则应尽量避免使用 release(),让 unique_ptr 自动管理对象的生命周期。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::unique_ptr<int> ptr = std::make_unique<int>(1300);
    2 int* rawPtr = ptr.release(); // Release ownership, ptr is now empty
    3 if (ptr) {
    4 std::cout << "ptr still owns an object." << std::endl; // This won't be printed
    5 } else {
    6 std::cout << "ptr is now empty after release()." << std::endl; // ptr is now empty
    7 }
    8 // Now rawPtr needs to be manually deleted to avoid memory leak
    9 delete rawPtr; // Manually delete the released raw pointer

    自定义删除器 (Custom Deleter): unique_ptr 允许使用自定义删除器,这在需要管理非内存资源或者使用特殊的内存释放方式时非常有用。自定义删除器可以在 unique_ptr 析构时被调用,执行用户定义的资源释放操作。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 // Custom deleter as a function
    5 void customDeleter(int* ptr) {
    6 std::cout << "Custom deleter called for value: " << *ptr << std::endl;
    7 delete ptr;
    8 }
    9
    10 int main() {
    11 // unique_ptr with function as deleter
    12 std::unique_ptr<int, decltype(&customDeleter)> ptr1(new int(1400), customDeleter);
    13
    14 // Custom deleter as a lambda expression
    15 auto lambdaDeleter = [](int* ptr) {
    16 std::cout << "Lambda deleter called for value: " << *ptr << std::endl;
    17 delete ptr;
    18 };
    19 std::unique_ptr<int, decltype(lambdaDeleter)> ptr2(new int(1500), lambdaDeleter);
    20
    21
    22 return 0;
    23 } // customDeleter and lambdaDeleter will be called when ptr1 and ptr2 go out of scope

    5.3 何时使用 shared_ptr, weak_ptr, unique_ptr (When to Use shared_ptr, weak_ptr, unique_ptr)

    选择合适的智能指针类型是编写高效且安全 C++ 代码的关键。shared_ptrweak_ptrunique_ptr 各有其特定的用途和适用场景。理解它们之间的差异,可以帮助我们做出正确的选择。 (Choosing the right smart pointer type is key to writing efficient and safe C++ code. shared_ptr, weak_ptr, and unique_ptr each have their specific uses and applicable scenarios. Understanding the differences between them can help us make the right choice.)

    5.3.1 选择合适的智能指针 (Choosing the Right Smart Pointer)

    在选择智能指针时,需要考虑以下几个关键因素:

    所有权模型 (Ownership Model):

    独占所有权 (Exclusive Ownership): 如果一个对象应该只被一个所有者拥有,并且其生命周期与所有者一致,那么 unique_ptr 是最佳选择。unique_ptr 提供了高效的资源管理,且开销最小。例如,函数内部动态分配的临时对象,或者类成员变量指向的独占拥有的对象,都适合使用 unique_ptr
    共享所有权 (Shared Ownership): 当多个对象需要共享对同一资源的所有权,且资源的生命周期应持续到最后一个所有者消失时,shared_ptr 是合适的选择。例如,在实现缓存、对象池或者多个组件共享同一数据时,可以使用 shared_ptr
    非拥有式观察 (Non-owning Observation): 当需要观察由 shared_ptr 管理的对象,但不参与所有权管理时,weak_ptr 是必要的。weak_ptr 用于打破循环引用,以及在观察者模式等场景中避免悬挂指针。

    性能考量 (Performance Considerations):

    unique_ptr: 性能开销最小,几乎与裸指针相当。没有引用计数的开销,移动操作非常高效。在性能敏感的场景中,如果所有权是独占的,优先选择 unique_ptr
    shared_ptr: 引入了引用计数的开销,包括引用计数的原子操作(在多线程环境下)。创建和拷贝 shared_ptrunique_ptr 稍慢。在确实需要共享所有权时,shared_ptr 的开销是合理的。
    weak_ptr: 开销比 shared_ptr 略小,因为它不增加引用计数。但 weak_ptr 需要通过 lock() 转换为 shared_ptr 才能访问对象,lock() 操作也可能存在一定的开销。

    使用场景 (Use Cases):

    unique_ptr:
    ▮▮▮▮⚝ 函数返回动态分配的对象,转移所有权给调用者。
    ▮▮▮▮⚝ 类成员变量管理独占拥有的资源。
    ▮▮▮▮⚝ 作为容器元素,管理容器内对象的生命周期。
    ▮▮▮▮⚝ 代替裸指针进行资源管理,提高代码的异常安全性。
    shared_ptr:
    ▮▮▮▮⚝ 实现共享资源的管理,例如多个模块共享配置对象或缓存数据。
    ▮▮▮▮⚝ 用于容器中,当容器中的对象需要被多个地方共享和访问时。
    ▮▮▮▮⚝ 在工厂模式中,工厂函数返回 shared_ptr,管理创建对象的生命周期。
    ▮▮▮▮⚝ 实现 Pimpl 惯用法 (Pimpl Idiom),管理实现细节的生命周期。
    weak_ptr:
    ▮▮▮▮⚝ 打破 shared_ptr 造成的循环引用。
    ▮▮▮▮⚝ 在观察者模式中,观察者使用 weak_ptr 观察主题,避免循环依赖。
    ▮▮▮▮⚝ 用于缓存设计,缓存项可以使用 weak_ptr 引用实际数据,当数据不再被其他 shared_ptr 引用时,缓存可以自动失效。

    以下是一个简单的决策流程图,帮助选择合适的智能指针:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 graph TD
    2 A[是否需要共享所有权?] -->| (Yes)| B[shared_ptr];
    3 A -->| (No)| C[是否需要独占所有权?];
    4 C -->| (Yes)| D[unique_ptr];
    5 C -->| (No)| E[不需要智能指针,考虑裸指针或引用];
    6 B --> F[需要观察但不拥有所有权?]
    7 F --> | (Yes)| G[weak_ptr];
    8 F --> | (No)| B
    9 D --> H[性能敏感?]
    10 H --> | (Yes)| D
    11 H --> |否 (No)| B

    5.3.2 最佳实践总结 (Summary of Best Practices)

    优先使用 unique_ptr (Prefer unique_ptr): 在不需要共享所有权的情况下,优先使用 unique_ptrunique_ptr 更轻量、更高效,且能清晰地表达独占所有权的语义。

    仅在必要时使用 shared_ptr (Use shared_ptr only when necessary): shared_ptr 引入了引用计数的开销,应仅在真正需要共享所有权时使用。过度使用 shared_ptr 可能会降低性能,并可能隐藏潜在的所有权设计问题。

    使用 weak_ptr 打破循环引用 (Use weak_ptr to break circular references): 当使用 shared_ptr 出现循环引用时,使用 weak_ptr 来打破循环,避免内存泄漏。通常将“从属”关系一方的 shared_ptr 改为 weak_ptr

    使用 make_uniquemake_shared 创建智能指针 (Use make_unique and make_shared to create smart pointers): 使用 std::make_unique<T>(...)std::make_shared<T>(...) 来创建 unique_ptrshared_ptr,可以提高效率和异常安全性。

    避免裸指针和手动 new/delete (Avoid raw pointers and manual new/delete): 尽可能使用智能指针来管理动态分配的内存,避免手动 new/delete,减少内存泄漏和悬挂指针的风险。

    清晰地设计所有权关系 (Design ownership relationships clearly): 在设计类和模块时,明确对象的所有权关系,选择合适的智能指针类型来管理资源。清晰的所有权设计是编写健壮、可维护代码的基础。

    通过合理选择和使用 shared_ptrweak_ptrunique_ptr,可以有效地管理 C++ 中的动态内存,编写出更安全、更高效、更易于维护的现代 C++ 代码。

    6. shared_ptr 的陷阱与最佳实践 (Pitfalls and Best Practices of shared_ptr)

    本章 discusses common pitfalls when using shared_ptr and provides best practices to avoid these issues and use shared_ptr effectively. (本章讨论了使用 shared_ptr 时的常见陷阱,并提供了避免这些问题并有效使用 shared_ptr 的最佳实践。)

    6.1 循环引用问题与解决方案 (Circular Dependency Issues and Solutions)

    本节 revisits the problem of circular references with shared_ptr and details solutions using weak_ptr to break these cycles. (本节回顾了 shared_ptr 的循环引用问题,并详细介绍了使用 weak_ptr 打破这些循环的解决方案。)

    6.1.1 循环引用的成因 (Causes of Circular References)

    循环引用 (circular reference) 是使用 shared_ptr 时最常见的陷阱之一,它会导致内存泄漏 (memory leak)。当两个或多个对象彼此持有 shared_ptr,形成一个环状的拥有关系时,就会发生循环引用。由于每个 shared_ptr 都维护着对象的引用计数 (reference counting),在循环引用中,环内的对象即使在外部不再被引用,它们的引用计数也永远不会降为零,从而导致对象无法被析构和释放,最终造成内存泄漏。

    考虑以下情景:类 A 和类 B 互相持有对方的 shared_ptr 成员。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class B; // Forward declaration of class B
    5
    6 class A {
    7 public:
    8 std::shared_ptr<B> b_ptr;
    9 ~A() {
    10 std::cout << "A 析构函数 (A destructor) 被调用 (called)" << std::endl;
    11 }
    12 };
    13
    14 class B {
    15 public:
    16 std::shared_ptr<A> a_ptr;
    17 ~B() {
    18 std::cout << "B 析构函数 (B destructor) 被调用 (called)" << std::endl;
    19 }
    20 };
    21
    22 int main() {
    23 std::shared_ptr<A> a = std::make_shared<A>();
    24 std::shared_ptr<B> b = std::make_shared<B>();
    25
    26 a->b_ptr = b;
    27 b->a_ptr = a;
    28
    29 // a 和 b 超出作用域 (a and b go out of scope)
    30 return 0;
    31 }

    在这个例子中,main 函数结束时,ab 这两个 shared_ptr 变量超出作用域,它们的引用计数会减少。然而,由于 a 持有 bshared_ptr,而 b 又持有 ashared_ptr,即使 ab 自身的引用计数降为 1,但由于互相引用,导致类 A 和类 B 实例的引用计数永远不会降至 0。因此,AB 的析构函数永远不会被调用,从而造成内存泄漏。

    总结循环引用产生的条件:

    ① 两个或多个对象之间存在相互持有的 shared_ptr 关系。
    ② 这种相互持有关系形成一个或多个环状结构。
    ③ 外部作用域对环内对象的直接 shared_ptr 引用消失后,环内对象由于环状引用,引用计数无法降为零。

    循环引用是需要特别注意的 shared_ptr 陷阱,理解其成因是解决问题的关键。在设计类之间的关系时,需要仔细考虑所有权 (ownership) 结构,避免不必要的双向 shared_ptr 引用。

    6.1.2 使用 weak_ptr 打破循环引用 (Using weak_ptr to Break Circular References)

    为了解决循环引用问题,C++ 引入了 weak_ptr (弱指针)。weak_ptr 是一种不控制对象生命周期的智能指针,它指向由 shared_ptr 管理的对象,但是不增加对象的引用计数。weak_ptr 可以用来打破 shared_ptr 形成的循环引用。

    当需要建立对象之间的关系,但又不希望形成所有权关系时,应该使用 weak_ptr。在循环引用场景中,将环状引用中的至少一个 shared_ptr 替换为 weak_ptr,就可以打破循环引用。

    修改之前的例子,将类 B 中的 a_ptrshared_ptr<A> 改为 weak_ptr<A>

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class B; // Forward declaration of class B
    5
    6 class A {
    7 public:
    8 std::shared_ptr<B> b_ptr;
    9 ~A() {
    10 std::cout << "A 析构函数 (A destructor) 被调用 (called)" << std::endl;
    11 }
    12 };
    13
    14 class B {
    15 public:
    16 std::weak_ptr<A> a_ptr; // 使用 weak_ptr
    17 ~B() {
    18 std::cout << "B 析构函数 (B destructor) 被调用 (called)" << std::endl;
    19 }
    20 };
    21
    22 int main() {
    23 std::shared_ptr<A> a = std::make_shared<A>();
    24 std::shared_ptr<B> b = std::make_shared<B>();
    25
    26 a->b_ptr = b;
    27 b->a_ptr = a; // b 持有指向 a 的 weak_ptr
    28
    29 // a 和 b 超出作用域 (a and b go out of scope)
    30 return 0;
    31 }

    在这个修改后的例子中,B 类不再通过 shared_ptr 拥有 A 对象,而是通过 weak_ptr 观察 A 对象。当 main 函数结束时,ab 超出作用域,最初的 shared_ptr 引用计数降为零。由于 b->a_ptrweak_ptr,它不增加 A 对象的引用计数。因此,当 a 的最后一个 shared_ptr 引用消失后,A 对象的引用计数变为 0,A 对象的析构函数被调用,随后 B 对象的析构函数也被调用。这样就避免了循环引用导致的内存泄漏。

    weak_ptr 的使用方法:

    创建 weak_ptr: weak_ptr 只能从 shared_ptr 或另一个 weak_ptr 构造或赋值。不能直接从裸指针 (raw pointer) 构造。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<int> sp = std::make_shared<int>(10);
    2 std::weak_ptr<int> wp1 = sp; // 从 shared_ptr 构造
    3 std::weak_ptr<int> wp2 = wp1; // 从 weak_ptr 构造

    lock() 方法: weak_ptr 不直接访问对象,需要调用 lock() 方法尝试获取其指向的 shared_ptrlock() 返回一个新的 shared_ptr
    ▮▮▮▮⚝ 如果原始对象仍然存在 (即 shared_ptr 引用计数大于 0),lock() 返回一个新的 shared_ptr,指向原始对象。此时,这个新 shared_ptr 会增加对象的引用计数。
    ▮▮▮▮⚝ 如果原始对象已经被销毁 (即所有 shared_ptr 都已释放,引用计数为 0),lock() 返回一个空的 shared_ptr (nullptr)。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::weak_ptr<int> wp;
    2 {
    3 std::shared_ptr<int> sp = std::make_shared<int>(20);
    4 wp = sp;
    5 std::shared_ptr<int> locked_sp = wp.lock(); // 获取 shared_ptr
    6 if (locked_sp) {
    7 std::cout << "对象仍然存在,值为 (Object still exists, value): " << *locked_sp << std::endl;
    8 }
    9 } // sp 超出作用域,但 wp 仍然有效
    10 std::shared_ptr<int> locked_sp_after_scope = wp.lock();
    11 if (!locked_sp_after_scope) {
    12 std::cout << "对象已被销毁 (Object has been destroyed)" << std::endl;
    13 }

    expired() 方法: 可以使用 expired() 方法检查 weak_ptr 指向的对象是否已经被销毁。如果对象已被销毁,expired() 返回 true,否则返回 false。这等价于检查 wp.lock() 是否返回空指针。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 if (wp.expired()) {
    2 std::cout << "weak_ptr 已过期 (weak_ptr has expired)" << std::endl;
    3 } else {
    4 std::cout << "weak_ptr 仍然有效 (weak_ptr is still valid)" << std::endl;
    5 }

    最佳实践:

    ⚝ 在设计类之间的关系时,优先考虑单向所有权 (unidirectional ownership)。
    ⚝ 当需要表示“拥有” (owns) 关系时,使用 shared_ptr
    ⚝ 当需要表示“知道” (knows about) 或 “观察” (observes) 关系,而不需要所有权时,使用 weak_ptr。特别是在可能出现循环引用的场景中,务必使用 weak_ptr 打破循环。
    ⚝ 避免在类设计中出现不必要的双向 shared_ptr 引用。仔细分析对象之间的生命周期依赖关系,合理使用 shared_ptrweak_ptr

    6.2 性能考量 (Performance Considerations)

    虽然 shared_ptr 提供了自动内存管理,极大地简化了资源管理并提高了代码的安全性,但是它并非没有性能开销 (performance overhead)。理解 shared_ptr 的性能特性,有助于在性能敏感的场景下做出更合理的选择和优化。

    6.2.1 引用计数的开销 (Overhead of Reference Counting)

    shared_ptr 的核心机制是引用计数 (reference counting)。每次 shared_ptr 的创建、拷贝、赋值和销毁,都会涉及到引用计数的增加和减少操作。这些操作通常需要原子操作 (atomic operations) 来保证线程安全 (thread safety)。原子操作相比于普通的非原子操作,通常具有更高的开销。

    引用计数开销的主要来源:

    原子操作: 引用计数的增加和减少必须是原子操作,以防止在多线程环境下出现数据竞争 (data race)。原子操作通常比非原子操作慢。
    控制块 (control block) 的分配和维护: shared_ptr 需要维护一个控制块来存储引用计数、弱引用计数和删除器 (deleter) 等信息。控制块的分配和销毁也会带来一定的开销。
    虚函数调用 (virtual function call) (当使用自定义删除器时): 如果 shared_ptr 使用了自定义删除器,并且删除器是函数对象或 lambda 表达式,可能会涉及到虚函数调用,这也会增加一定的开销。

    性能影响的场景:

    频繁的 shared_ptr 拷贝和赋值: 如果代码中存在大量的 shared_ptr 拷贝和赋值操作,引用计数的原子操作开销会累积起来,对性能产生一定影响。
    多线程环境: 在高并发的多线程环境中,多个线程同时操作同一个 shared_ptr 的引用计数,原子操作的竞争 (contention) 可能会更加激烈,导致性能下降。
    大型对象或复杂对象: 虽然 shared_ptr 本身的开销相对固定,但是如果 shared_ptr 管理的是大型对象或构造和析构开销很大的对象,那么 shared_ptr 的管理开销相对于对象的自身开销来说可能就变得不那么显著。

    unique_ptr 的性能比较:

    unique_ptr (独占指针) 没有引用计数,它的开销非常小,几乎与裸指针相当。unique_ptr 的创建、移动和销毁通常只需要少量的指令。因此,在不需要共享所有权,只需要独占所有权 (exclusive ownership) 的场景下,unique_ptr 通常是性能更高的选择。

    总结:

    shared_ptr 的引用计数机制确实会带来一定的性能开销,尤其是在高频操作和多线程环境下。但是,这种开销通常是可以接受的,并且相对于手动内存管理带来的风险和复杂性来说,使用 shared_ptr 的优势更加明显。在大多数应用场景下,shared_ptr 的性能开销不会成为瓶颈。只有在极少数性能极其敏感的场景下,才需要仔细评估 shared_ptr 的性能影响,并考虑是否可以使用 unique_ptr 或其他性能更高的资源管理方式。

    6.2.2 性能优化技巧 (Performance Optimization Tips)

    虽然 shared_ptr 的性能开销通常可以接受,但在某些性能敏感的场景下,仍然可以采取一些技巧来优化 shared_ptr 的性能,或者避免不必要的开销。

    优先使用 make_shared: make_shared<T>(...) 是创建 shared_ptr 的推荐方式。它将对象的内存分配和控制块 (control block) 的内存分配合并为一次分配,减少了内存分配的次数,提高了效率。此外,make_shared 还可以防止在某些异常情况下可能发生的内存泄漏,具有异常安全性 (exception safety)。

    不推荐的做法:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<MyClass> ptr(new MyClass()); // 两次内存分配

    推荐的做法:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 auto ptr = std::make_shared<MyClass>(); // 一次内存分配,更高效,且异常安全

    避免不必要的 shared_ptr 拷贝: shared_ptr 的拷贝会增加引用计数。在不需要共享所有权的情况下,尽量避免不必要的 shared_ptr 拷贝。可以通过传递裸指针或引用 (reference) 来避免拷贝 shared_ptr

    示例: 如果一个函数只需要使用对象,而不需要拥有对象的所有权,可以考虑传递裸指针或对象的引用,而不是 shared_ptr

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void processObject(MyClass* obj) { // 接受裸指针
    2 if (obj) {
    3 obj->doSomething();
    4 }
    5 }
    6
    7 void processObjectRef(MyClass& obj) { // 接受引用
    8 obj.doSomething();
    9 }
    10
    11 int main() {
    12 auto ptr = std::make_shared<MyClass>();
    13 processObject(ptr.get()); // 传递裸指针
    14 processObjectRef(*ptr); // 传递引用
    15 return 0;
    16 }

    注意: 传递裸指针或引用时,必须确保在函数调用的过程中,原始对象的生命周期是有效的。否则可能导致悬挂指针 (dangling pointer) 或访问已销毁的对象。

    使用移动语义 (move semantics) 转移所有权: shared_ptr 支持移动语义。可以使用 std::move 来转移 shared_ptr 的所有权,避免引用计数的增加和减少开销。移动操作通常只是指针的复制,开销很小。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<MyClass> createObject() {
    2 return std::make_shared<MyClass>();
    3 }
    4
    5 int main() {
    6 std::shared_ptr<MyClass> ptr1 = createObject(); // 创建 shared_ptr
    7 std::shared_ptr<MyClass> ptr2 = std::move(ptr1); // 移动所有权,ptr1 变为空
    8 // ptr2 现在拥有对象的所有权,ptr1 不再拥有
    9 return 0;
    10 }

    在局部作用域 (local scope) 使用 shared_ptr: 尽量在需要共享所有权的作用域内使用 shared_ptr,一旦超出作用域,shared_ptr 会自动释放,减少对象的生命周期管理负担。

    考虑使用 weak_ptr 观察对象: 如果只需要观察对象,而不需要拥有对象的所有权,可以使用 weak_ptrweak_ptr 的开销比 shared_ptr 更小,因为它不增加引用计数。

    自定义内存分配器 (custom allocator) (高级技巧): 在某些极端性能敏感的场景下,可以考虑使用自定义内存分配器来优化 shared_ptr 的内存分配和释放过程。但这通常是高级优化技巧,只在非常特定的情况下才需要考虑。

    总结:

    性能优化是一个权衡 (trade-off) 的过程。在优化 shared_ptr 性能的同时,也需要考虑代码的可读性、可维护性和安全性。在大多数情况下,使用 make_shared 和避免不必要的拷贝已经可以获得很好的性能。只有在性能瓶颈确实出现在 shared_ptr 的操作上时,才需要考虑更复杂的优化技巧。在进行任何性能优化之前,务必进行性能测试 (performance testing) 和分析 (profiling),确定优化的重点和方向。

    6.3 避免裸指针与 new/delete (Avoiding Raw Pointers and new/delete)

    现代 C++ 强调资源获取即初始化 (RAII, Resource Acquisition Is Initialization) 原则和智能指针 (smart pointers) 的使用,以实现自动资源管理,避免手动内存管理带来的风险。shared_ptr 作为一种重要的智能指针,其最佳实践之一就是尽可能避免使用裸指针 (raw pointers) 和手动 new/delete

    6.3.1 现代 C++ 资源管理原则 (Modern C++ Resource Management Principles)

    现代 C++ 资源管理的核心原则是 RAII。RAII 的基本思想是:将资源的生命周期与对象的生命周期绑定。当对象被创建时,资源被自动获取 (初始化);当对象被销毁时,资源被自动释放 (清理)。通过 RAII,可以确保资源在任何情况下 (包括异常发生时) 都能得到正确释放,从而避免资源泄漏 (resource leak) 和其他资源管理问题。

    智能指针是 RAII 原则在内存管理方面的具体应用。shared_ptrunique_ptrweak_ptr 等智能指针类都遵循 RAII 原则,它们在构造函数中获取内存 (或其他资源),在析构函数中自动释放内存 (或其他资源)。

    避免手动内存管理的原因:

    内存泄漏 (memory leak) 的风险: 手动使用 newdelete 进行内存管理,很容易因为忘记 delete 或在异常处理中遗漏 delete 而导致内存泄漏。长时间运行的程序中,内存泄漏会逐渐消耗系统资源,最终导致程序崩溃或系统性能下降。
    悬挂指针 (dangling pointer) 的风险: 如果 delete 释放了内存,但是仍然存在指针指向这块已释放的内存,那么这个指针就变成了悬挂指针。访问悬挂指针会导致未定义行为 (undefined behavior),可能引发程序崩溃、数据损坏或其他难以预测的问题。
    异常安全性 (exception safety) 问题: 在手动内存管理的代码中,如果 newdelete 之间发生了异常,可能会导致内存泄漏或资源未释放。使用智能指针可以更好地保证异常安全性。
    代码复杂性和维护性 (complexity and maintainability): 手动内存管理的代码通常更加复杂,容易出错,并且难以维护。使用智能指针可以简化代码,提高代码的可读性和可维护性。

    现代 C++ 推荐的资源管理方式:

    优先使用智能指针: 对于动态分配的内存,应该优先使用智能指针 (shared_ptr, unique_ptr, weak_ptr) 进行管理,而不是裸指针和手动 new/delete
    RAII 封装其他资源: 除了内存,其他资源 (如文件句柄 (file handle)、网络连接 (network connection)、互斥锁 (mutex lock) 等) 也应该使用 RAII 原则进行管理。可以使用自定义的 RAII 类或标准库提供的 RAII 类 (如 std::unique_lock, std::lock_guard, std::fstream 等) 来管理这些资源。
    最小化裸指针的使用: 裸指针主要用于以下场景:
    ▮▮▮▮⚝ 观察 (non-owning) 指针:用于函数参数传递,或在智能指针已经管理内存的情况下,临时访问对象,但不转移所有权。
    ▮▮▮▮⚝ 与旧的 C 风格 API 或库 (legacy C-style APIs or libraries) 交互。
    ▮▮▮▮⚝ 在性能极其敏感的底层代码中,并且经过仔细的性能分析和权衡后。
    ▮▮▮▮⚝ 在智能指针的内部实现中。

    在其他情况下,应该尽量避免直接使用裸指针,特别是拥有所有权的裸指针。

    6.3.2 代码示例与重构 (Code Examples and Refactoring)

    通过代码示例,展示如何将使用裸指针和手动 new/delete 的代码重构为使用 shared_ptr 的代码,以提高代码的安全性和可维护性。

    重构前的代码 (使用裸指针和 new/delete):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2
    3 class MyClass {
    4 public:
    5 MyClass(int value) : value_(value) {
    6 std::cout << "MyClass 构造函数 (constructor) 被调用 (called), value = " << value_ << std::endl;
    7 }
    8 ~MyClass() {
    9 std::cout << "MyClass 析构函数 (destructor) 被调用 (called), value = " << value_ << std::endl;
    10 }
    11 void doSomething() const {
    12 std::cout << "Doing something, value = " << value_ << std::endl;
    13 }
    14 private:
    15 int value_;
    16 };
    17
    18 void processObject(MyClass* obj) {
    19 if (obj) {
    20 obj->doSomething();
    21 }
    22 }
    23
    24 int main() {
    25 MyClass* objPtr = new MyClass(42); // 手动分配内存
    26 processObject(objPtr);
    27 delete objPtr; // 手动释放内存
    28 objPtr = nullptr;
    29 return 0;
    30 }

    这段代码使用了裸指针 MyClass* objPtr 和手动 new/delete 进行内存管理。如果 processObject 函数抛出异常,或者在 delete objPtr; 之前程序提前退出,就可能发生内存泄漏。

    重构后的代码 (使用 shared_ptr):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <memory>
    3
    4 class MyClass {
    5 public:
    6 MyClass(int value) : value_(value) {
    7 std::cout << "MyClass 构造函数 (constructor) 被调用 (called), value = " << value_ << std::endl;
    8 }
    9 ~MyClass() {
    10 std::cout << "MyClass 析构函数 (destructor) 被调用 (called), value = " << value_ << std::endl;
    11 }
    12 void doSomething() const {
    13 std::cout << "Doing something, value = " << value_ << std::endl;
    14 }
    15 private:
    16 int value_;
    17 };
    18
    19 void processObject(std::shared_ptr<MyClass> objPtr) { // 接受 shared_ptr
    20 if (objPtr) {
    21 objPtr->doSomething();
    22 }
    23 }
    24
    25 int main() {
    26 auto objPtr = std::make_shared<MyClass>(42); // 使用 make_shared 创建 shared_ptr
    27 processObject(objPtr); // 传递 shared_ptr,发生拷贝,引用计数增加
    28 // objPtr 超出作用域,引用计数减少,当最后一个 shared_ptr 消失时,内存自动释放
    29 return 0;
    30 }

    重构后的代码使用 std::make_shared<MyClass>(42) 创建 shared_ptr,并将 processObject 函数的参数类型改为 std::shared_ptr<MyClass>. 这样,内存的分配和释放都由 shared_ptr 自动管理,无需手动 delete。即使 processObject 函数抛出异常,或者程序提前退出,shared_ptr 也能确保内存被正确释放,避免了内存泄漏和悬挂指针的风险。

    重构步骤总结:

    查找代码中所有使用 new 分配内存的地方: 识别出所有手动分配内存的代码行。
    将裸指针替换为 shared_ptr: 将声明为裸指针的变量类型改为 std::shared_ptr<T>.
    使用 make_shared 创建 shared_ptr: 将 new T(...) 替换为 std::make_shared<T>(...) 来创建 shared_ptr
    移除手动 delete: 删除所有显式的 delete 语句。
    检查函数参数和返回值: 根据需要,将函数参数和返回值类型从裸指针改为 shared_ptrweak_ptr,并考虑所有权转移和共享的需求。

    通过以上重构步骤,可以将旧代码中手动内存管理的部分转换为使用 shared_ptr 进行自动内存管理,提高代码的安全性、可靠性和可维护性,符合现代 C++ 的编程风格和最佳实践。

    6.4 代码审查与 shared_ptr 的使用规范 (Code Review and Usage Guidelines of shared_ptr)

    代码审查 (code review) 是保证代码质量的重要环节。在团队开发中,制定和遵循 shared_ptr 的使用规范,并在代码审查过程中重点关注 shared_ptr 的使用,可以有效地避免潜在的问题,提高代码的健壮性和一致性。

    6.4.1 代码审查 checklist (Code Review Checklist)

    在代码审查中,可以参考以下 checklist,检查代码中 shared_ptr 的使用是否规范和正确:

    是否优先使用 make_shared 创建 shared_ptr?:
    ▮▮▮▮⚝ 检查代码中是否使用了 std::shared_ptr<T>(new T(...)) 这种创建方式。
    ▮▮▮▮⚝ 建议: 强制使用 make_shared,除非有特殊原因 (例如需要自定义分配器)。

    是否存在潜在的循环引用风险?:
    ▮▮▮▮⚝ 检查类之间是否存在双向 shared_ptr 引用关系。
    ▮▮▮▮⚝ 检查是否存在可能形成环状引用的复杂对象关系。
    ▮▮▮▮⚝ 建议: 优先使用 weak_ptr 打破循环引用,或重新设计对象关系,避免循环依赖。

    是否在不需要共享所有权的情况下使用了 shared_ptr?:
    ▮▮▮▮⚝ 检查是否可以用 unique_ptr 替代 shared_ptr
    ▮▮▮▮⚝ 建议: 在只需要独占所有权时,优先使用 unique_ptr,性能更高,语义更清晰。

    是否避免了不必要的 shared_ptr 拷贝?:
    ▮▮▮▮⚝ 检查函数参数传递是否需要拷贝 shared_ptr
    ▮▮▮▮⚝ 检查是否可以通过传递裸指针、引用或使用移动语义来避免不必要的拷贝。
    ▮▮▮▮⚝ 建议: 按需传递参数,避免不必要的拷贝开销。

    是否正确使用了 weak_ptr?:
    ▮▮▮▮⚝ 检查 weak_ptr 是否用于观察对象,而不是拥有对象。
    ▮▮▮▮⚝ 检查在使用 weak_ptr 前是否调用了 lock() 方法,并检查了返回的 shared_ptr 是否为空。
    ▮▮▮▮⚝ 建议: 确保 weak_ptr 的使用场景正确,并正确处理 lock() 的结果。

    是否仍然存在裸指针和手动 new/delete?:
    ▮▮▮▮⚝ 检查代码中是否仍然存在手动 newdelete 的使用。
    ▮▮▮▮⚝ 建议: 尽可能使用智能指针替代裸指针和手动内存管理。对于遗留代码,逐步重构为使用智能指针。

    自定义删除器 (custom deleter) 的使用是否正确?:
    ▮▮▮▮⚝ 如果使用了自定义删除器,检查删除器逻辑是否正确,是否处理了所有需要释放的资源。
    ▮▮▮▮⚝ 检查自定义删除器是否与 shared_ptr 管理的对象类型匹配。
    ▮▮▮▮⚝ 建议: 谨慎使用自定义删除器,确保删除器逻辑的正确性。

    线程安全性 (thread safety) 考虑:
    ▮▮▮▮⚝ 在多线程环境下,检查对 shared_ptr 的操作是否线程安全。
    ▮▮▮▮⚝ 建议: shared_ptr 自身的引用计数操作是线程安全的,但需要注意 shared_ptr 管理的对象本身是否线程安全。

    代码风格一致性 (code style consistency):
    ▮▮▮▮⚝ 检查 shared_ptr 的命名和使用方式是否符合团队的代码风格规范。
    ▮▮▮▮⚝ 建议: 制定统一的 shared_ptr 使用规范,并在代码审查中强制执行。

    6.4.2 团队开发规范 (Team Development Guidelines)

    为了在团队开发中更好地使用 shared_ptr,建议制定以下团队开发规范:

    强制使用 make_shared: 在创建 shared_ptr 时,除非有特殊理由,必须使用 std::make_shared<T>(...),禁止使用 std::shared_ptr<T>(new T(...)) 的形式。

    优先使用 unique_ptr: 在只需要独占所有权时,优先使用 unique_ptr。只有在需要共享所有权时才使用 shared_ptr

    合理使用 weak_ptr: 在需要观察对象但不拥有所有权,或者需要打破循环引用时,使用 weak_ptr。避免滥用 weak_ptr,只在合适的场景下使用。

    避免裸指针和手动 new/delete: 除非与旧的 C 风格 API 交互或在极少数性能敏感的场景下,禁止在新的代码中使用裸指针和手动 new/delete。对于遗留代码,逐步重构为使用智能指针。

    代码审查 shared_ptr 使用: 在代码审查过程中,必须重点关注 shared_ptr 的使用,按照代码审查 checklist 进行检查,确保 shared_ptr 的使用规范和正确。

    培训和知识共享 (training and knowledge sharing): 团队内部定期进行智能指针和现代 C++ 资源管理方面的培训和知识共享,提高团队成员对 shared_ptr 的理解和应用能力。

    自动化检查工具 (automated checking tools): 考虑使用静态代码分析工具 (static code analysis tools) 来自动检查代码中 shared_ptr 的使用是否符合规范,并发现潜在的问题。

    持续改进 (continuous improvement): 团队根据项目实践和经验反馈,不断完善 shared_ptr 的使用规范,并定期回顾和更新规范内容。

    通过制定和执行这些团队开发规范,可以有效地提高团队代码中 shared_ptr 的使用质量,减少错误,提高代码的可维护性和协作效率,最终提升整个项目的质量和开发效率。

    Appendix A: 参考文献 (References)

    本附录 provides a list of references including C++ standard documents, influential books on C++, and relevant research papers on smart pointers and memory management. (本附录提供了参考文献列表,包括 C++ 标准文档、C++ 方面的有影响力的书籍以及关于智能指针和内存管理的相关研究论文。)

    A.1 C++ 标准文档 (C++ Standard Documents)

    本节列出 C++ 标准文档,这些文档是理解 shared_ptr 以及 C++ 语言规范的权威来源。(This section lists the C++ standard documents, which are the authoritative sources for understanding shared_ptr and the C++ language specification.)

    ISO/IEC 14882:2011 (C++11 Standard)
    ▮▮▮▮ 这是引入 shared_ptr 的最初标准,详细定义了智能指针的行为和规范。(This is the initial standard that introduced shared_ptr, detailing its behavior and specifications.)
    ISO/IEC 14882:2014 (C++14 Standard)
    ▮▮▮▮ C++14 标准对智能指针进行了一些小的改进和澄清。(The C++14 standard made minor improvements and clarifications to smart pointers.)
    ISO/IEC 14882:2017 (C++17 Standard)
    ▮▮▮▮ C++17 标准继续完善智能指针,并引入了如类模板参数推导 (class template argument deduction) 等新特性,可能间接影响 shared_ptr 的使用。(The C++17 standard continued to refine smart pointers and introduced new features such as class template argument deduction, which may indirectly affect the use of shared_ptr.)
    ISO/IEC 14882:2020 (C++20 Standard)
    ▮▮▮▮ C++20 标准引入了更多现代 C++ 特性,持续改进语言和库,值得参考最新的标准文档。(The C++20 standard introduced even more modern C++ features, continuously improving the language and library, and it is worth referring to the latest standard documents.)
    Working Draft, Standard for Programming Language C++ — [Date of Latest Draft] (可以查阅最新的 C++ 标准草案)
    ▮▮▮▮ 最新的草案 (working draft) 反映了 C++ 标准的最新进展,对于关注前沿技术和标准演进的读者非常有价值。(The latest working draft reflects the latest progress in the C++ standard and is very valuable for readers who are interested in cutting-edge technologies and standard evolution.)

    A.2 书籍 (Books)

    本节推荐一些深入探讨 C++ 编程,特别是智能指针和内存管理的重要书籍。(This section recommends some important books that delve into C++ programming, especially smart pointers and memory management.)

    《Effective C++》 (Effective C++) by Scott Meyers ✍️
    ▮▮▮▮ 经典 C++ 编程指南,提供了许多关于如何更有效地使用 C++ 的实践建议,虽然出版较早,但其中关于资源管理和异常安全的原则仍然非常重要。(A classic C++ programming guide that provides many practical suggestions on how to use C++ more effectively. Although published earlier, its principles on resource management and exception safety are still very important.)
    《More Effective C++》 (More Effective C++) by Scott Meyers ✍️
    ▮▮▮▮ 《Effective C++》的续作,更深入地探讨了 C++ 的高级主题,包括内存管理和智能指针。(A sequel to "Effective C++", delving deeper into advanced C++ topics, including memory management and smart pointers.)
    《Effective Modern C++》 (Effective Modern C++) by Scott Meyers ✍️
    ▮▮▮▮ 专注于 C++11 和 C++14 的新特性,包括智能指针的最佳实践和使用指南,是学习现代 C++ 智能指针不可或缺的参考书。(Focuses on the new features of C++11 and C++14, including best practices and usage guidelines for smart pointers. It is an indispensable reference book for learning modern C++ smart pointers.)
    《C++ Primer》 (C++ Primer) by Stanley B. Lippman, Josée Lajoie, and Barbara E. Moo 📚
    ▮▮▮▮ 一本全面的 C++ 入门教程,详细介绍了 C++ 语言的各个方面,包括智能指针的原理和使用。(A comprehensive C++ introductory tutorial that details all aspects of the C++ language, including the principles and usage of smart pointers.)
    《The C++ Programming Language》 (The C++ Programming Language) by Bjarne Stroustrup 🚀
    ▮▮▮▮ C++ 语言的设计者撰写的权威著作,全面而深入地介绍了 C++ 语言的各个方面,是深入理解 C++ 的必备参考。(An authoritative work written by the designer of the C++ language, comprehensively and deeply introducing all aspects of the C++ language. It is an essential reference for in-depth understanding of C++.)
    《Modern C++ Design: Generic Programming and Design Patterns Applied》 (Modern C++ Design: Generic Programming and Design Patterns Applied) by Andrei Alexandrescu 💡
    ▮▮▮▮ 探讨了现代 C++ 的高级设计技术,包括泛型编程和设计模式,其中也涉及了智能指针在现代 C++ 设计中的应用。(Explores advanced design techniques in modern C++, including generic programming and design patterns, and also involves the application of smart pointers in modern C++ design.)
    《CppCon Presentations》 (CppCon Presentations) (CppCon 大会的演讲视频) 🖥️
    ▮▮▮▮ CppCon 是一个重要的 C++ 开发者大会,其发布的演讲视频包含了大量关于现代 C++ 实践、包括智能指针的深入讨论和案例分析,是学习和了解 C++ 最新技术的宝贵资源。(CppCon is an important C++ developer conference. Its released presentation videos contain a large number of in-depth discussions and case studies on modern C++ practices, including smart pointers. It is a valuable resource for learning and understanding the latest C++ technologies.)

    A.3 论文 (Papers)

    本节收录与智能指针和内存管理相关的学术论文,帮助读者从更学术的角度理解 shared_ptr 的设计和实现。(This section includes academic papers related to smart pointers and memory management, helping readers understand the design and implementation of shared_ptr from a more academic perspective.)

    "A Proposal to Add Garbage Collection and Pointer Semantics to C++" by Hans-J. Boehm, et al. 📝
    ▮▮▮▮⚝ 虽然 C++ 最终选择了智能指针而不是垃圾回收,但这篇论文探讨了 C++ 中内存管理的不同方法,包括智能指针的早期思想。(Although C++ ultimately chose smart pointers instead of garbage collection, this paper explores different approaches to memory management in C++, including early ideas about smart pointers.)
    "Resource Management in C++: RAII and Smart Pointers" by Bjarne Stroustrup ✍️
    ▮▮▮▮⚝ C++ 语言设计者本人撰写的关于 RAII 和智能指针的文章,深入解释了 C++ 中资源管理的基本原则和智能指针的作用。(An article on RAII and smart pointers written by the designer of the C++ language himself, deeply explaining the basic principles of resource management and the role of smart pointers in C++.)
    "Exception-Safe Smart Pointers" by Herb Sutter and Andrei Alexandrescu 🛡️
    ▮▮▮▮⚝ 探讨了如何设计异常安全的智能指针,以及智能指针在编写异常安全代码中的作用,强调了 shared_ptr 在异常安全编程中的重要性。(Discusses how to design exception-safe smart pointers and the role of smart pointers in writing exception-safe code, emphasizing the importance of shared_ptr in exception-safe programming.)
    "Empirical Study of Memory Management Schemes" by Benjamin Zorn 📊
    ▮▮▮▮⚝ 一篇关于不同内存管理方案的实证研究论文,虽然不完全专注于智能指针,但提供了内存管理领域的一些背景知识,有助于理解智能指针设计的上下文。(An empirical research paper on different memory management schemes. Although not entirely focused on smart pointers, it provides some background knowledge in the field of memory management, which helps to understand the context of smart pointer design.)
    "Understanding and Applying Move Semantics in C++11" by Howard E. Hinnant 🚀
    ▮▮▮▮⚝ 虽然主要关于移动语义 (move semantics),但移动语义与智能指针(特别是 unique_ptrshared_ptr 的高效使用)密切相关,理解移动语义有助于更好地使用智能指针。(Although mainly about move semantics, move semantics are closely related to smart pointers (especially the efficient use of unique_ptr and shared_ptr). Understanding move semantics helps to use smart pointers better.)

    这些参考文献为读者提供了从不同角度深入了解 C++ shared_ptr 的资源,无论是从语言标准、实践指南还是学术研究的角度,都能获得更全面的认识。(These references provide readers with resources to understand C++ shared_ptr in depth from different perspectives. Whether from the perspective of language standards, practical guidelines, or academic research, readers can gain a more comprehensive understanding.)

    Appendix B: 术语表 (Glossary)

    Appendix B: 术语表 (Glossary)

    本附录定义了与 shared_ptr 和智能指针相关的关键术语,帮助读者理解本书中使用的术语。(This appendix defines key terms related to shared_ptr and smart pointers, aiding readers in understanding the terminology used throughout the book.)

    智能指针 (Smart Pointer)
    ▮▮▮▮一种 C++ 对象,它像指针一样工作,但提供了自动内存管理的功能,以防止内存泄漏和其他资源管理问题。智能指针通过封装裸指针并自动管理其生命周期来实现资源安全。 (A C++ object that acts like a pointer but provides automatic memory management to prevent memory leaks and other resource management issues. Smart pointers achieve resource safety by encapsulating raw pointers and automatically managing their lifecycle.)

    裸指针 (Raw Pointer)
    ▮▮▮▮在 C++ 中,裸指针是指通过 new 运算符分配的内存地址,需要手动使用 delete 运算符释放。与智能指针不同,裸指针不提供自动内存管理,容易导致内存泄漏和悬挂指针等问题。(In C++, a raw pointer refers to a memory address allocated using the new operator, which requires manual deallocation using the delete operator. Unlike smart pointers, raw pointers do not provide automatic memory management and are prone to issues such as memory leaks and dangling pointers.)

    引用计数 (Reference Counting)
    ▮▮▮▮一种内存管理技术,用于跟踪有多少个智能指针共享同一个对象。当最后一个指向对象的智能指针被销毁时,对象的内存才会被自动释放。shared_ptr 使用引用计数来管理共享对象的生命周期。(A memory management technique used to track how many smart pointers are sharing the same object. The object's memory is automatically released only when the last smart pointer pointing to it is destroyed. shared_ptr uses reference counting to manage the lifecycle of shared objects.)

    控制块 (Control Block)
    ▮▮▮▮shared_ptr 内部用于管理引用计数、弱计数和删除器的数据结构。控制块与被管理对象分开分配,允许多个 shared_ptr 实例共享所有权信息。(A data structure internal to shared_ptr used to manage reference counts, weak counts, and the deleter. The control block is allocated separately from the managed object, allowing multiple shared_ptr instances to share ownership information.)

    独占所有权 (Exclusive Ownership)
    ▮▮▮▮一种所有权模型,其中只有一个智能指针可以指向特定的对象。unique_ptr 实现了独占所有权,确保只有一个指针负责对象的生命周期。当 unique_ptr 被销毁或所有权转移时,它所管理的对象也会被销毁。(An ownership model where only one smart pointer can point to a specific object. unique_ptr implements exclusive ownership, ensuring that only one pointer is responsible for the object's lifecycle. When the unique_ptr is destroyed or ownership is transferred, the object it manages is also destroyed.)

    共享所有权 (Shared Ownership)
    ▮▮▮▮一种所有权模型,允许多个智能指针同时指向同一个对象,并共享对象的生命周期管理责任。shared_ptr 实现了共享所有权,通过引用计数来跟踪所有权数量,并在最后一个 shared_ptr 销毁时释放对象。(An ownership model that allows multiple smart pointers to point to the same object simultaneously and share the responsibility of managing the object's lifecycle. shared_ptr implements shared ownership, using reference counting to track the number of owners and releasing the object when the last shared_ptr is destroyed.)

    弱引用 (Weak Reference)
    ▮▮▮▮一种不增加对象引用计数的引用方式。weak_ptr 提供对 shared_ptr 管理的对象的访问,但不参与所有权管理。weak_ptr 主要用于观察对象是否存在,以及打破循环引用。(A type of reference that does not increment the object's reference count. weak_ptr provides access to objects managed by shared_ptr but does not participate in ownership management. weak_ptr is primarily used to observe object existence and break circular references.)

    自定义删除器 (Custom Deleter)
    ▮▮▮▮一个用户提供的函数或函数对象,在智能指针管理的对象的生命周期结束时被调用,用于执行特定的资源清理操作,而不仅仅是简单的 delete 操作。自定义删除器允许智能指针管理各种类型的资源,而不仅仅是动态分配的内存。(A user-provided function or function object that is called when the lifecycle of an object managed by a smart pointer ends, used to perform specific resource cleanup operations beyond just a simple delete. Custom deleters allow smart pointers to manage various types of resources, not just dynamically allocated memory.)

    循环引用 (Circular Reference)
    ▮▮▮▮当两个或多个对象彼此持有 shared_ptr 形成环状依赖关系时,即使这些对象不再被程序的其他部分使用,它们的引用计数也永远不会降为零,从而导致内存泄漏。weak_ptr 通常用于打破循环引用。(Occurs when two or more objects hold shared_ptrs to each other, creating a circular dependency. Even if these objects are no longer used by other parts of the program, their reference counts will never drop to zero, leading to memory leaks. weak_ptr is commonly used to break circular references.)

    RAII (Resource Acquisition Is Initialization)
    ▮▮▮▮资源获取即初始化,是 C++ 中一种重要的资源管理原则。它提倡在对象构造时获取资源(例如内存、文件句柄、锁),并在对象析构时释放资源。智能指针是 RAII 原则的典型应用,它们在构造时“获取”指向动态分配内存的所有权,并在析构时自动“释放”内存。(Resource Acquisition Is Initialization, an important resource management principle in C++. It advocates for acquiring resources (e.g., memory, file handles, locks) during object construction and releasing them during object destruction. Smart pointers are a typical application of the RAII principle; they "acquire" ownership of dynamically allocated memory during construction and automatically "release" the memory during destruction.)

    内存泄漏 (Memory Leak)
    ▮▮▮▮当动态分配的内存不再被程序使用,但未能被正确释放时,就会发生内存泄漏。长期运行的程序中的内存泄漏会导致程序逐渐消耗完所有可用内存,最终崩溃或性能下降。智能指针旨在自动管理内存,从而减少内存泄漏的风险。(A memory leak occurs when dynamically allocated memory is no longer used by the program but is not properly released. Memory leaks in long-running programs can cause the program to gradually consume all available memory, eventually leading to crashes or performance degradation. Smart pointers are designed to automatically manage memory, thus reducing the risk of memory leaks.)

    悬挂指针 (Dangling Pointer)
    ▮▮▮▮一个指向已经被释放或无效内存区域的指针。解引用悬挂指针会导致未定义行为,包括程序崩溃、数据损坏等。智能指针通过自动管理对象的生命周期,显著降低了悬挂指针的风险。(A pointer that points to memory that has been freed or is no longer valid. Dereferencing a dangling pointer leads to undefined behavior, including program crashes and data corruption. Smart pointers significantly reduce the risk of dangling pointers by automatically managing object lifecycles.)

    原子操作 (Atomic Operations)
    ▮▮▮▮不可中断的操作,即使在多线程环境下,也能保证操作的完整性。shared_ptr 的引用计数操作使用原子操作来实现线程安全,确保在多线程并发访问时引用计数的正确性。(Indivisible operations that guarantee integrity even in a multithreaded environment. shared_ptr's reference count operations use atomic operations to achieve thread safety, ensuring the correctness of reference counts when accessed concurrently by multiple threads.)

    多态删除 (Polymorphic Deletion)
    ▮▮▮▮当使用基类指针指向派生类对象,并需要通过基类指针删除对象时,为了确保调用正确的派生类析构函数,基类的析构函数必须是虚函数。shared_ptr 在管理多态对象时,依赖于虚析构函数来实现正确的对象销毁。(When using a base class pointer to point to a derived class object, and the object needs to be deleted through the base class pointer, the base class destructor must be virtual to ensure that the correct derived class destructor is called. shared_ptr relies on virtual destructors to achieve correct object destruction when managing polymorphic objects.)

    别名 shared_ptr (Aliasing shared_ptr)
    ▮▮▮▮一种 shared_ptr 的构造方式,它允许创建一个新的 shared_ptr 实例,该实例与原始 shared_ptr 共享所有权和控制块,但指向原始对象的一个子对象或不同的相关对象。别名 shared_ptr 不会增加原始对象的引用计数。(A way of constructing a shared_ptr that allows creating a new shared_ptr instance that shares ownership and the control block with the original shared_ptr, but points to a subobject of the original object or a different related object. Aliasing shared_ptr does not increment the original object's reference count.)

    Pimpl 惯用法 (Pimpl Idiom)
    ▮▮▮▮Pointer to implementation,一种 C++ 编程技巧,用于隐藏类的实现细节,减少编译依赖,并提高封装性。Pimpl 惯用法通常使用一个私有指针成员指向实际的实现类,而智能指针(如 unique_ptrshared_ptr)常用于管理这个实现指针的生命周期。(Pointer to implementation, a C++ programming technique used to hide the implementation details of a class, reduce compile-time dependencies, and improve encapsulation. The Pimpl idiom typically uses a private pointer member to point to the actual implementation class, and smart pointers (such as unique_ptr or shared_ptr) are often used to manage the lifecycle of this implementation pointer.)

    移动语义 (Move Semantics)
    ▮▮▮▮C++11 引入的一种优化机制,允许资源的所有权在对象之间高效转移,而无需深拷贝。unique_ptr 依赖于移动语义来实现所有权的转移,因为 unique_ptr 不支持拷贝,只支持移动。(An optimization mechanism introduced in C++11 that allows the efficient transfer of resource ownership between objects without deep copying. unique_ptr relies on move semantics to implement ownership transfer because unique_ptr does not support copying, only moving.)

    资源管理 (Resource Management)
    ▮▮▮▮在程序中有效地分配、使用和释放各种资源(例如内存、文件句柄、网络连接等)的过程。良好的资源管理是编写可靠和高性能程序的关键。智能指针是 C++ 中实现自动资源管理的重要工具。(The process of effectively allocating, using, and releasing various resources (e.g., memory, file handles, network connections, etc.) in a program. Good resource management is crucial for writing reliable and high-performance programs. Smart pointers are an important tool in C++ for achieving automatic resource management.)

    异常安全 (Exception Safety)
    ▮▮▮▮程序在遇到异常时,仍能保持资源正确管理,避免资源泄漏和数据损坏的能力。智能指针通过 RAII 原则,在异常发生时也能确保自动释放资源,从而提高程序的异常安全性。(The ability of a program to maintain correct resource management and avoid resource leaks and data corruption when exceptions occur. Smart pointers, through the RAII principle, ensure automatic resource release even in the event of exceptions, thereby improving the exception safety of programs.)

    Appendix C: shared_ptr 相关面试题 (Interview Questions Related to shared_ptr)

    本附录提供了一系列与 shared_ptr 相关的常见面试问题以及建议的答案,有助于求职者为 C++ 开发人员角色做准备。(This appendix presents a collection of common interview questions related to shared_ptr along with suggested answers, helpful for job seekers preparing for C++ developer roles.)

    Appendix C1: 基础概念题 (Basic Concept Questions)

    Appendix C1.1: 什么是智能指针?为什么要使用智能指针?(What is a Smart Pointer? Why Use Smart Pointers?)

    问题: 请解释什么是智能指针,并说明在 C++ 中为什么要使用智能指针,尤其是在内存管理方面。(Please explain what a smart pointer is and why smart pointers are used in C++, especially in terms of memory management.)

    答案:

    智能指针 (Smart Pointer) 是 C++ 中用于自动管理动态分配内存的对象,它们模仿指针的行为,但提供了自动内存管理的功能,从而避免了手动内存管理中常见的内存泄漏 (Memory Leak) 和悬挂指针 (Dangling Pointer) 问题。

    为什么要使用智能指针:

    自动内存管理 (Automatic Memory Management): 智能指针在对象不再被使用时,会自动释放其管理的内存,无需程序员显式调用 delete。这极大地减少了内存泄漏的风险。

    RAII (Resource Acquisition Is Initialization): 智能指针是 RAII 原则的典型应用。资源(例如内存)的获取与对象的生命周期绑定,当智能指针对象销毁时,会自动释放资源。

    异常安全 (Exception Safety): 在手动内存管理中,如果 newdelete 之间发生异常,可能导致内存泄漏。智能指针保证即使在异常情况下,也能正确释放内存,从而提高代码的异常安全性。

    避免悬挂指针 (Avoiding Dangling Pointers): 智能指针在所管理的对象被删除后,会自动变为 nullptr 或失效状态(例如 weak_ptr 的过期状态),从而避免了访问已释放内存的悬挂指针问题。

    代码可读性和可维护性 (Code Readability and Maintainability): 使用智能指针可以使代码更简洁、更易读,并降低了手动内存管理的复杂性,从而提高代码的可维护性。

    总而言之,智能指针通过自动化资源管理,提高了 C++ 程序的安全性、可靠性和可维护性,是现代 C++ 编程中不可或缺的工具。

    Appendix C1.2: shared_ptr 的核心机制是什么?(What is the Core Mechanism of shared_ptr?)

    问题: 请解释 shared_ptr 的核心机制,包括引用计数和共享所有权。(Please explain the core mechanism of shared_ptr, including reference counting and shared ownership.)

    答案:

    shared_ptr 的核心机制是引用计数 (Reference Counting)共享所有权 (Shared Ownership)

    引用计数 (Reference Counting):
    ▮▮▮▮⚝ shared_ptr 内部维护着一个引用计数器 (Reference Counter),用于跟踪有多少个 shared_ptr 实例共享同一个对象的所有权。
    ▮▮▮▮⚝ 当创建一个新的 shared_ptr 指向某个对象,或者通过拷贝、赋值等操作增加 shared_ptr 的数量时,引用计数器会原子性地递增 (Atomically Increment)
    ▮▮▮▮⚝ 当一个 shared_ptr 对象被销毁(例如超出作用域)时,引用计数器会原子性地递减 (Atomically Decrement)
    ▮▮▮▮⚝ 当引用计数器降为零 (Reaches Zero) 时,意味着没有任何 shared_ptr 指向该对象,此时 shared_ptr 会负责析构 (Destruct) 所管理的对象,并释放其占用的内存。

    共享所有权 (Shared Ownership):
    ▮▮▮▮⚝ shared_ptr 允许多个 shared_ptr 实例共享 (Share) 同一个动态分配对象的所有权。
    ▮▮▮▮⚝ 这意味着多个 shared_ptr 可以同时指向同一个对象,并且对象的生命周期由所有指向它的 shared_ptr 共同管理。
    ▮▮▮▮⚝ 只有当最后一个指向该对象的 shared_ptr 被销毁时,对象才会被释放。

    总结: shared_ptr 通过引用计数实现了共享所有权,使得多个智能指针可以安全地共同管理同一个对象的生命周期,避免了手动内存管理的复杂性和潜在错误。引用计数的原子性操作保证了在多线程环境下的线程安全 (Thread Safety)。

    Appendix C1.3: shared_ptr 与 unique_ptr 和 weak_ptr 的区别是什么?(What are the Differences between shared_ptr, unique_ptr, and weak_ptr?)

    问题: 请比较 shared_ptrunique_ptrweak_ptr 这三种智能指针,说明它们各自的用途和所有权模型。(Please compare shared_ptr, unique_ptr, and weak_ptr, explaining their respective uses and ownership models.)

    答案:

    shared_ptrunique_ptrweak_ptr 是 C++ 中三种主要的智能指针,它们各自具有不同的用途和所有权模型,适用于不同的场景。

    特性 (Feature)unique_ptrshared_ptrweak_ptr
    所有权模型 (Ownership Model)独占所有权 (Exclusive Ownership)共享所有权 (Shared Ownership)弱引用,不参与所有权 (Weak Reference, Non-owning)
    用途 (Purpose)管理独占拥有的动态分配对象,防止内存泄漏。(Manage exclusively owned dynamically allocated objects, preventing memory leaks.)管理共享拥有的动态分配对象,允许多个指针共享所有权。(Manage shared dynamically allocated objects, allowing multiple pointers to share ownership.)观察 shared_ptr 管理的对象,但不增加引用计数,用于打破循环引用。(Observe objects managed by shared_ptr without increasing the reference count, used for breaking circular references.)
    拷贝与赋值 (Copy and Assignment)不可拷贝,可移动 (Non-copyable, Moveable)可拷贝,可赋值 (Copyable, Assignable)可拷贝,可赋值 (Copyable, Assignable)
    底层机制 (Underlying Mechanism)无引用计数,轻量级。(No reference counting, lightweight.)引用计数。(Reference counting.)不直接管理对象,依赖于 shared_ptr 的控制块。(Does not directly manage objects, relies on shared_ptr's control block.)
    使用场景 (Use Cases)- 函数返回局部动态分配对象。
    - 实现独占所有权的资源管理。
    - 作为容器元素,管理独占对象。( - Returning locally dynamically allocated objects from functions.
    - Implementing resource management with exclusive ownership.
    - As container elements, managing exclusive objects.)
    - 多个对象需要共享同一个资源。
    - 实现缓存、工厂模式等。
    - 循环数据结构的节点。( - Multiple objects need to share the same resource.
    - Implementing caches, factory patterns, etc.
    - Nodes in circular data structures.)
    - 打破 shared_ptr 循环引用。
    - 观察者模式中,观察者需要引用主题但不影响主题生命周期。
    - 缓存失效检测。( - Breaking shared_ptr circular references.
    - In observer patterns, observers need to refer to subjects without affecting subject lifetime.
    - Cache invalidation detection.)
    性能开销 (Performance Overhead)几乎无开销,与裸指针相当。(Almost no overhead, comparable to raw pointers.)相对 unique_ptr 有一定开销,因为需要维护引用计数。(Relatively more overhead than unique_ptr due to reference counting.)开销较小,主要开销在 lock() 操作时。(Small overhead, main overhead is during lock() operation.)

    总结:

    unique_ptr 适用于独占所有权的场景,保证资源在离开作用域后被及时释放,性能开销小,是首选的智能指针类型,如果所有权是唯一的,优先使用 unique_ptr
    shared_ptr 适用于共享所有权的场景,允许多个指针共同管理对象的生命周期,适用于需要共享资源的复杂场景,但有引用计数的开销。
    weak_ptr 用于解决 shared_ptr循环引用问题,或者在需要观察 shared_ptr 管理对象但不参与所有权时使用,它不增加引用计数,不会阻止对象的释放。

    选择哪种智能指针取决于具体的应用场景和所有权需求。在现代 C++ 开发中,应尽量避免使用裸指针和手动内存管理,优先使用智能指针来管理资源。

    Appendix C2: 深入理解题 (In-depth Understanding Questions)

    Appendix C2.1: make_shared 的优势是什么?为什么推荐使用 make_shared 创建 shared_ptr?(What are the Advantages of make_shared? Why is it Recommended to Use make_shared to Create shared_ptr?)

    问题: 请解释 make_shared 函数相比于直接使用 shared_ptr 构造函数,在创建 shared_ptr 时有哪些优势?为什么推荐使用 make_shared?(Please explain the advantages of using make_shared compared to directly using the shared_ptr constructor when creating shared_ptr. Why is it recommended to use make_shared?)

    答案:

    make_shared 是一个模板函数,用于同时分配 (Allocate Together) 对象本身和 shared_ptr控制块 (Control Block)。相比于分别分配对象和控制块的传统 shared_ptr 构造方式,make_shared 具有以下主要优势:

    性能提升 (Performance Improvement):
    ▮▮▮▮⚝ 减少内存分配次数 (Reduced Memory Allocation Count): make_shared 只进行一次内存分配 (Single Allocation),即可为对象本身和控制块分配内存。而使用 shared_ptr 构造函数(例如 shared_ptr<T>(new T()))通常需要两次内存分配 (Two Allocations):一次为对象分配内存,一次为控制块分配内存。减少内存分配次数可以提高性能,尤其是在频繁创建 shared_ptr 的场景下。
    ▮▮▮▮⚝ 减少控制块的间接访问 (Reduced Indirect Access to Control Block): make_shared 分配的内存通常是连续的 (Contiguous),对象和控制块在同一块内存区域,可以减少指针的间接访问,提高缓存命中率,从而提升性能。

    异常安全性 (Exception Safety):
    ▮▮▮▮⚝ 避免潜在的内存泄漏 (Prevent Potential Memory Leaks): 考虑以下使用 shared_ptr 构造函数的代码:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 process_data(shared_ptr<Data>(new Data()), function_that_may_throw());

    在这个例子中,new Data() 先被执行,分配了 Data 对象的内存。然后 shared_ptr<Data> 的构造函数准备被调用。但是,如果在 new Data()shared_ptr 构造函数之间,function_that_may_throw() 抛出了异常,那么之前分配的 Data 对象的内存将无法被 shared_ptr 管理,从而导致内存泄漏 (Memory Leak)
    ▮▮▮▮⚝ 使用 make_shared 可以避免这个问题,因为对象和控制块的分配是原子性的 (Atomic),要么都成功,要么都失败。如果分配失败,不会有中间状态,因此不会有内存泄漏的风险。

    更简洁的代码 (More Concise Code):
    ▮▮▮▮⚝ make_shared 的语法更加简洁,可以直接在函数调用中创建 shared_ptr,代码更易读。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 使用 make_shared
    2 auto ptr1 = make_shared<Data>(arg1, arg2);
    3
    4 // 使用 shared_ptr 构造函数
    5 auto ptr2 = shared_ptr<Data>(new Data(arg1, arg2));

    为什么推荐使用 make_shared:

    基于以上优势,make_shared 通常是创建 shared_ptr首选方法 (Preferred Method)。除非有特殊需求(例如需要自定义删除器,且删除器需要访问控制块),否则应尽可能使用 make_shared 来创建 shared_ptr,以获得更好的性能和异常安全性。

    总结: make_shared 通过一次内存分配、提高缓存命中率和增强异常安全性,成为创建 shared_ptr 的最佳实践。在现代 C++ 开发中,应优先考虑使用 make_shared

    Appendix C2.2: shared_ptr 的线程安全性如何?(How Thread-safe is shared_ptr?)

    问题: 请讨论 shared_ptr 的线程安全性,哪些操作是线程安全的?哪些操作可能存在线程安全问题?(Please discuss the thread safety of shared_ptr. Which operations are thread-safe? Which operations might have thread safety issues?)

    答案:

    shared_ptr 在多线程环境下,其引用计数 (Reference Count) 的操作是线程安全 (Thread-safe) 的。这意味着多个线程可以同时安全地增加或减少同一个 shared_ptr 实例的引用计数,而不会导致数据竞争 (Data Race) 或未定义行为 (Undefined Behavior)。

    线程安全的操作:

    引用计数的增减 (Increment and Decrement of Reference Count):
    ▮▮▮▮⚝ shared_ptr 的拷贝构造函数 (Copy Constructor)、拷贝赋值运算符 (Copy Assignment Operator)、析构函数 (Destructor) 等操作,都会原子性地增加或减少引用计数,这些操作是线程安全的。
    ▮▮▮▮⚝ 多个线程可以同时拷贝、赋值或销毁指向同一对象的 shared_ptr,而不会出现问题。

    use_count() 方法 (use_count() Method):
    ▮▮▮▮⚝ use_count() 方法返回当前 shared_ptr 的引用计数。虽然 use_count() 本身是原子操作,但其返回值在多线程环境下可能不是完全准确的 (Not Always Perfectly Accurate)。因为在调用 use_count() 返回值之后,其他线程可能已经修改了引用计数。因此,use_count() 主要用于调试 (Debugging)统计 (Statistics),而不是用于程序逻辑的判断。

    可能存在线程安全问题的操作:

    通过 get() 获取原始指针并访问对象 (Accessing the Managed Object through Raw Pointer Obtained by get()):
    ▮▮▮▮⚝ shared_ptr::get() 方法返回 shared_ptr 管理的原始指针。直接通过这个原始指针访问对象 (Directly Accessing the Object through this Raw Pointer) 是线程不安全的 (Thread-unsafe)
    ▮▮▮▮⚝ shared_ptr 只保证引用计数的线程安全,不保证所管理对象本身的线程安全。如果多个线程通过 get() 获取的原始指针同时访问和修改对象的状态,可能会导致数据竞争。
    ▮▮▮▮⚝ 正确做法 (Correct Approach): 如果需要在多线程环境下访问 shared_ptr 管理的对象,应该始终通过 shared_ptr 本身进行操作 (Operate through the shared_ptr itself),例如使用 *ptrptr->member,而不是使用 ptr.get() 获取的原始指针。

    非 const 的 shared_ptr 对象的读写 (Read and Write Operations on Non-const shared_ptr Objects):
    ▮▮▮▮⚝ 虽然引用计数的操作是线程安全的,但是shared_ptr 对象本身进行非 const 的读写操作 (Non-const Read and Write Operations on the shared_ptr object itself) 仍然需要同步 (Synchronization)
    ▮▮▮▮⚝ 例如,如果多个线程同时修改同一个 shared_ptr 对象(例如赋值操作),即使它们指向同一个对象,也需要使用互斥锁 (Mutex) 等同步机制来保护 shared_ptr 对象本身的操作。

    总结:

    shared_ptr引用计数操作是线程安全的,可以安全地在多线程环境下进行拷贝、赋值和销毁。
    通过 get() 获取原始指针并访问对象是线程不安全的,应避免直接使用原始指针进行多线程访问。
    对非 const 的 shared_ptr 对象本身进行读写操作需要同步,以避免数据竞争。
    ⚝ 在多线程环境下使用 shared_ptr 时,应主要关注所管理对象本身的线程安全性 (Thread Safety of the Managed Object itself),并确保对 shared_ptr 对象的正确同步操作。

    Appendix C2.3: 什么是循环引用?shared_ptr 如何导致循环引用?如何解决循环引用问题?(What is a Circular Reference? How can shared_ptr Lead to Circular References? How to Solve Circular Reference Issues?)

    问题: 请解释什么是循环引用 (Circular Reference),shared_ptr 如何导致循环引用,以及如何使用 weak_ptr 解决循环引用问题。(Please explain what a circular reference is, how shared_ptr can lead to circular references, and how to use weak_ptr to solve circular reference issues.)

    答案:

    循环引用 (Circular Reference) 指的是两个或多个对象之间相互持有 shared_ptr 指针,形成一个闭环的引用关系,导致它们的引用计数永远无法降为零,从而无法被自动释放,最终造成内存泄漏 (Memory Leak)

    shared_ptr 导致循环引用的场景:

    循环引用通常发生在双向关联 (Bidirectional Association) 的对象之间,例如父子关系、互相引用的对象等。

    考虑以下例子,类 A 和类 B 互相持有对方的 shared_ptr

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class B; // 前向声明 (Forward Declaration)
    2
    3 class A {
    4 public:
    5 shared_ptr<B> ptr_b;
    6 ~A() { cout << "A destructor" << endl; }
    7 };
    8
    9 class B {
    10 public:
    11 shared_ptr<A> ptr_a;
    12 ~B() { cout << "B destructor" << endl; }
    13 };
    14
    15 int main() {
    16 shared_ptr<A> sp_a = make_shared<A>();
    17 shared_ptr<B> sp_b = make_shared<B>();
    18
    19 sp_a->ptr_b = sp_b; // A 指向 B
    20 sp_b->ptr_a = sp_a; // B 指向 A
    21
    22 // sp_a 和 sp_b 超出作用域,析构函数不会被调用,内存泄漏发生
    23 return 0;
    24 }

    在这个例子中,sp_asp_b 相互指向对方,形成循环引用。当 main 函数结束时,sp_asp_b 超出作用域,它们的引用计数分别从 1 减为 0,但由于它们互相引用,类 A 和类 B 实例的引用计数仍然为 1,永远不会降为 0,因此它们的析构函数不会被调用,造成了内存泄漏。

    使用 weak_ptr 解决循环引用问题:

    要解决循环引用问题,需要打破循环引用环中的至少一个强引用 (Strong Reference),将其改为弱引用 (Weak Reference)weak_ptr 正是用于解决循环引用问题的智能指针。

    在上述例子中,可以将类 B 中的 ptr_ashared_ptr<A> 改为 weak_ptr<A>

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class B {
    2 public:
    3 weak_ptr<A> ptr_a; // 改为 weak_ptr
    4 ~B() { cout << "B destructor" << endl; }
    5 };

    修改后,B 对象不再强引用 A 对象,循环引用被打破。当 main 函数结束时,sp_a 的引用计数降为 0,A 对象被析构,A 对象析构时,B 对象的引用计数也降为 0,B 对象也被析构,内存得到正确释放。

    weak_ptr 的作用:

    weak_ptr 指向由 shared_ptr 管理的对象,但不增加引用计数 (Does not Increase Reference Count)
    weak_ptr 不拥有 (Does not Own) 对象的所有权,不会影响对象的生命周期。
    weak_ptr 可以观察 (Observe) 对象是否仍然存活。通过 weak_ptr::lock() 方法可以尝试获取一个指向对象的 shared_ptr。如果对象仍然存活,lock() 返回一个有效的 shared_ptr,否则返回一个空的 shared_ptr

    解决循环引用的最佳实践:

    在设计类之间的关系时,如果存在双向关联,应仔细考虑所有权关系。通常情况下,应该让单向 (Unidirectional) 的关系使用 shared_ptr,而反向 (Reverse) 的关系使用 weak_ptr,以避免循环引用。例如,在父子关系中,父对象可以使用 shared_ptr 管理子对象,而子对象可以使用 weak_ptr 指向父对象。

    总结: 循环引用是使用 shared_ptr 时需要注意的陷阱。通过使用 weak_ptr 打破循环引用环中的强引用,可以有效地解决循环引用问题,避免内存泄漏。理解 shared_ptrweak_ptr 的所有权模型和使用场景,是编写安全可靠 C++ 代码的关键。

    Appendix C3: 高级应用题 (Advanced Application Questions)

    Appendix C3.1: 如何为 shared_ptr 定制删除器?(How to Customize Deleters for shared_ptr?)

    问题: 请解释如何为 shared_ptr 定制删除器 (Custom Deleter),并说明定制删除器的应用场景。(Please explain how to customize deleters for shared_ptr and describe the application scenarios of custom deleters.)

    答案:

    shared_ptr 允许用户定制删除器 (Custom Deleter),即在 shared_ptr 管理的对象不再被需要时,执行自定义的资源释放操作,而不是简单的 delete 操作。定制删除器可以通过多种方式实现,例如函数指针 (Function Pointer)、函数对象 (Function Object) 和 Lambda 表达式 (Lambda Expression)。

    定制删除器的方法:

    使用函数指针 (Function Pointer):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void custom_deleter(int* ptr) {
    2 cout << "Custom deleter called for int*" << endl;
    3 delete ptr;
    4 }
    5
    6 int main() {
    7 shared_ptr<int> sp(new int(10), custom_deleter);
    8 return 0;
    9 }

    在这个例子中,custom_deleter 函数作为删除器,当 sp 的引用计数降为 0 时,custom_deleter 函数会被调用,执行自定义的删除操作。

    使用函数对象 (Function Object):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 struct FileDeleter {
    2 void operator()(FILE* fp) {
    3 if (fp) {
    4 cout << "Custom deleter called for FILE*" << endl;
    5 fclose(fp);
    6 }
    7 }
    8 };
    9
    10 int main() {
    11 shared_ptr<FILE> fp(fopen("test.txt", "r"), FileDeleter());
    12 if (fp) {
    13 // ... 使用文件 ...
    14 }
    15 return 0;
    16 }

    FileDeleter 是一个函数对象,其 operator() 重载函数作为删除器。当 fp 的引用计数降为 0 时,FileDeleteroperator() 会被调用,关闭文件句柄。

    使用 Lambda 表达式 (Lambda Expression):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 int main() {
    2 shared_ptr<int> sp(new int(20), [](int* ptr) {
    3 cout << "Lambda deleter called for int*" << endl;
    4 delete ptr;
    5 });
    6 return 0;
    7 }

    Lambda 表达式提供了一种简洁的方式定义内联的删除器。

    定制删除器的应用场景:

    管理非内存资源 (Managing Non-Memory Resources):
    ▮▮▮▮⚝ shared_ptr 不仅可以管理动态分配的内存,还可以管理其他类型的资源,例如文件句柄 (File Handle)、网络连接 (Network Connection)、互斥锁 (Mutex Lock) 等。
    ▮▮▮▮⚝ 对于这些非内存资源,资源的释放操作通常不是简单的 delete,而是需要调用特定的释放函数(例如 fclose()close()unlock() 等)。
    ▮▮▮▮⚝ 定制删除器可以确保在 shared_ptr 销毁时,正确地释放这些非内存资源,遵循 RAII 原则。

    管理数组 (Managing Arrays):
    ▮▮▮▮⚝ shared_ptr 默认使用 delete 释放内存,对于动态分配的数组,应该使用 delete[] 释放。
    ▮▮▮▮⚝ 可以通过定制删除器,使用 delete[] 来释放数组内存,避免内存泄漏或未定义行为。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 shared_ptr<int> array_ptr(new int[10], [](int* ptr) {
    2 cout << "Array deleter called for int[]" << endl;
    3 delete[] ptr;
    4 });

    注意 (Note): C++17 引入了 std::shared_ptr<T[]> 支持数组的 shared_ptr,可以自动选择正确的 delete[] 删除器,通常情况下,优先使用 std::shared_ptr<T[]>

    资源池 (Resource Pool):
    ▮▮▮▮⚝ 在资源池的场景下,资源的释放可能不是真正的 delete 操作,而是将资源放回资源池中以便复用。
    ▮▮▮▮⚝ 定制删除器可以实现将资源放回资源池的逻辑,而不是直接释放资源。

    日志记录或调试 (Logging or Debugging):
    ▮▮▮▮⚝ 定制删除器可以在资源释放时执行一些额外的操作,例如记录日志、进行调试信息输出等,方便程序的调试和监控。

    总结: 定制删除器是 shared_ptr 的一个强大特性,使得 shared_ptr 可以管理各种类型的资源,并执行自定义的资源释放操作。合理使用定制删除器可以提高代码的灵活性和资源管理的精细度。

    Appendix C3.2: 别名 shared_ptr (Aliasing shared_ptr) 是什么?有什么应用场景?(What is Aliasing shared_ptr? What are its Application Scenarios?)

    问题: 请解释什么是别名 shared_ptr (Aliasing shared_ptr),以及别名 shared_ptr 的应用场景。(Please explain what aliasing shared_ptr is and its application scenarios.)

    答案:

    别名 shared_ptr (Aliasing shared_ptr) 是一种特殊的 shared_ptr,它共享 (Shares) 另一个 shared_ptr所有权 (Ownership)控制块 (Control Block),但是指向 (Points to) 不同的对象或子对象。别名 shared_ptr 不会增加引用计数,但会延长原始 shared_ptr 管理的对象的生命周期。

    创建别名 shared_ptr 的方法:

    别名 shared_ptr 主要通过 shared_ptr别名构造函数 (Aliasing Constructor) 创建:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 template<typename Y, typename D>
    2 shared_ptr(const shared_ptr<Y, D>& r, element_type* ptr) noexcept;

    这个构造函数接受两个参数:

    r: 一个已存在的 shared_ptr,别名 shared_ptr 将共享 r 的所有权和控制块。
    ptr: 一个裸指针,指向别名 shared_ptr 要指向的对象。ptr 必须是 r 管理对象的一部分,或者是 r 管理对象本身。

    别名 shared_ptr 的应用场景:

    指向对象的成员或子对象 (Pointing to Members or Subobjects of an Object):
    ▮▮▮▮⚝ 当需要共享一个对象的生命周期,但只需要访问对象的某个成员或子对象时,可以使用别名 shared_ptr
    ▮▮▮▮⚝ 例如,有一个类 Outer,包含一个成员 Inner,我们想共享 Outer 对象的生命周期,但只需要一个 shared_ptr 指向 Inner 对象。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class Inner {
    2 public:
    3 int value;
    4 Inner(int v) : value(v) {}
    5 ~Inner() { cout << "Inner destructor" << endl; }
    6 };
    7
    8 class Outer {
    9 public:
    10 shared_ptr<Inner> inner_ptr;
    11 Outer(int v) : inner_ptr(make_shared<Inner>(v)) {}
    12 ~Outer() { cout << "Outer destructor" << endl; }
    13 };
    14
    15 int main() {
    16 shared_ptr<Outer> outer_ptr = make_shared<Outer>(100);
    17 shared_ptr<Inner> aliased_inner_ptr(outer_ptr, outer_ptr->inner_ptr.get()); // 创建别名 shared_ptr
    18
    19 cout << "Inner value: " << aliased_inner_ptr->value << endl;
    20
    21 // outer_ptr 和 aliased_inner_ptr 共享 Outer 对象的生命周期
    22 return 0;
    23 }

    在这个例子中,aliased_inner_ptr 是一个别名 shared_ptr,它共享 outer_ptr 的控制块,但指向的是 Outer 对象的成员 inner_ptr 所指向的 Inner 对象。即使 outer_ptr 超出作用域,只要 aliased_inner_ptr 还在作用域内,Outer 对象和 Inner 对象都不会被释放。

    实现观察者模式 (Implementing Observer Pattern):
    ▮▮▮▮⚝ 在观察者模式中,观察者 (Observer) 需要观察主题 (Subject) 的状态变化。可以使用别名 shared_ptr 让观察者持有对主题某个状态的引用,同时共享主题的生命周期。

    访问 Pimpl 惯用法的实现细节 (Accessing Implementation Details in Pimpl Idiom):
    ▮▮▮▮⚝ 在 Pimpl (Pointer to Implementation) 惯用法中,公有接口类通常持有一个指向私有实现类的指针。可以使用别名 shared_ptr 在公有接口类中提供访问私有实现类成员的接口,同时保持封装性。

    别名 shared_ptr 的注意事项:

    ⚝ 别名 shared_ptr 不拥有独立的所有权 (Does not Have Independent Ownership),它的生命周期完全依赖于原始 shared_ptr
    ⚝ 别名 shared_ptr 指向的指针 ptr 必须是原始 shared_ptr 管理对象的一部分,或者是指向原始 shared_ptr 管理对象本身的指针。否则,可能导致未定义行为。
    ⚝ 使用别名 shared_ptr 时,需要仔细考虑对象的所有权和生命周期管理,确保别名 shared_ptr 的使用是安全和正确的。

    总结: 别名 shared_ptr 是一种高级的 shared_ptr 用法,它允许在共享所有权的同时,指向不同的对象或子对象。别名 shared_ptr 在指向对象成员、实现观察者模式和 Pimpl 惯用法等场景下非常有用,但需要谨慎使用,确保正确管理对象的生命周期。