购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.6 管理资源的艺术:智能指针的解析

在前面的章节中,我们介绍了智能指针在资源管理中的基础应用,展示了它们如何利用模板机制简化内存管理。本节将继续这个讨论,深入探讨智能指针的设计与实现,揭示它们如何在C++中实现自动资源管理,并保证资源的安全使用。

2.6.1 智能指针的设计与实现:自动化资源管理

智能指针在C++中的设计充分体现了面向对象和泛型编程的原则之一——提供自动化的资源管理。通过封装原生指针,并在适当的时候自动释放所管理的资源,智能指针确保了资源使用的安全性和便捷性。

1.设计理念
1)指针的背景

在计算机科学中,指针是一种变量,其值为另一变量的地址,即直接指向内存中的某个位置。在C/C++编程中理解指针的概念至关重要。通过指针,程序可以存取和操作内存中的数据,包括数组、结构体和函数。指针提供了一种强大的工具,支持动态内存管理、数据结构(如链表和树)的实现,以及高效的函数调用和回调。

(1)动态内存管理

在早期的编程实践中,对内存的直接管理是常见的需求。C++提供了动态内存分配的能力,允许程序在运行时根据需要分配或释放内存。指针在这一过程中扮演了核心角色,它们使得开发者能够引用和操作这些动态分配的内存区域。

(2)问题与挑战

尽管指针为编程提供了极大的灵活性和控制力,但它们的直接使用也带来了诸多挑战和风险:

·内存泄漏:如果忘记释放已分配的内存,会导致内存泄漏,这是一种资源浪费,可能会随着时间的推移导致程序运行缓慢甚至崩溃。

·野指针和悬挂指针:未初始化的指针(野指针)或指向已释放内存的指针(悬挂指针)可能会导致不可预测的行为或程序崩溃。

·指针算术错误:错误的指针算术操作可能导致对内存的非法访问,破坏程序的数据完整性。

·所有权和生命周期问题:在复杂的应用中,管理动态分配内存的所有权和生命周期变得非常困难,尤其当资源需要在多个对象或函数间共享时。

【示例展示】

以下示例展示如何使用原始指针进行内存的分配和释放。

在这种情况下,程序员需要显式地管理内存,包括如何正确分配和释放内存,并确保处理悬挂指针的问题。这些挑战凸显了对于更安全和更自动化的内存管理机制的需求,而这也正是智能指针应运而生的背景。智能指针旨在解决自动化内存管理的常见问题,同时保持C++的性能优势和灵活性。

2)智能指针的引入

智能指针的引入是C++针对直接使用原生指针所带来的诸多挑战给出的解决方案。这些挑战包括内存泄漏、野指针、悬挂指针、指针算术错误以及资源所有权和生命周期管理的复杂性。通过自动化内存管理,智能指针减少了手动操作new和delete引起的内存泄漏风险,提升了代码的安全性和简洁性,使代码更易于理解和维护。

在类型安全和错误预防方面,智能指针继承了C++的类型安全特性,通过与原生指针类似的接口提供安全保障,并限制某些具有潜在危险的操作。例如禁止将智能指针隐式转换为不相关类型的指针,这有助于编译时而非运行时发现错误。

智能指针还通过std::unique_ptr、std::shared_ptr和std::weak_ptr等不同类型明确了资源所有权和生命周期管理。这种明确的资源管理策略减轻了开发者在复杂系统中管理资源生命周期的负担,还提高了系统的可维护性。

总之,智能指针的引入不仅提升了C++在资源管理方面的自动化程度,还体现了其核心设计哲学,如性能、灵活性、类型安全和错误预防。这些特性使C++继续作为一种高效、可靠并适用于多种编程场景的语言,有效响应了现代软件开发中对安全性和高效性的需求。

3)智能指针的设计

智能指针的设计体现了C++对安全、灵活且高效的资源管理的承诺。std::unique_ptr、std::shared_ptr和std::weak_ptr 3种智能指针各自承担不同的角色,以满足不同场景下的需求。下面将探讨每种智能指针的设计理念,以及它们如何相互补充,共同构成C++资源管理的全貌。

(1)std::unique_ptr的独占所有权

std::unique_ptr代表对一个资源的独占所有权,其设计理念基于“资源仅有一个所有者”的原则,确保资源的生命周期明确且易于追踪。std::unique_ptr自动释放其所管理的资源,当指针对象超出作用域或被显式删除时,避免了资源泄漏的风险。它的轻量级实现保持了对性能的关注,无须额外的引用计数开销,使得std::unique_ptr成为单一所有者场景下的理想选择,如函数内部临时资源的管理或作为类成员管理动态分配的资源。

(2)std::shared_ptr的共享所有权

与std::unique_ptr不同,std::shared_ptr引入了共享所有权的概念,允许多个std::shared_ptr实例共同拥有一个资源。通过引用计数机制,它确保资源在最后一个拥有者被销毁时才被释放,从而支持复杂数据结构(如图和树)的构建,以及跨系统部分共享资源。std::shared_ptr的设计响应了在多个对象间安全共享资源的需求,同时处理了生命周期管理的复杂性。然而,引用计数机制引入了性能考量,特别是在多线程环境下对计数器的更新需要加锁,这可能导致性能下降。

(3)std::weak_ptr的辅助角色

std::weak_ptr设计为std::shared_ptr的补充,解决了由共享所有权可能导致的循环引用问题,即由于两个或多个std::shared_ptr相互引用而导致资源永远不会被释放。std::weak_ptr允许访问std::shared_ptr管理的资源,而不增加引用计数,从而使得资源的所有者能够被适当销毁。

std::weak_ptr在需要监视资源生命周期而不拥有资源的场景下非常有用,如在缓存实现或观察者模式中。

(4)设计理念的一致性

这3种智能指针共同体现了C++的设计理念:通过提供灵活的资源管理工具,同时保持代码的安全性、清晰性和高效性。std::unique_ptr、std::shared_ptr和std::weak_ptr的设计考虑了不同场景的资源管理需求和挑战,从单一所有权到共享所有权,再到资源的非拥有性引用,提供了一套完整的解决方案。通过这些工具,C++程序员可以根据具体需求选择最适合的资源管理策略,有效避免常见的内存管理错误,如内存泄漏和野指针,从而编写出更安全和更可维护的代码。

4)C++20的变革

在C++20的更新中,引入了一个特别有用的功能:std::atomic<std::shared_ptr<T>>。这是对std::shared_ptr<T>的一种部分模板特化,允许程序员在多线程环境中以原子方式操作shared_ptr对象。传统上,如果多个线程需要访问和修改同一个shared_ptr对象但没有适当的同步机制,就可能会引发数据竞争,特别是当这些访问中涉及shared_ptr的非常量成员函数时。为了解决这个问题,C++20提供了std::atomic<std::shared_ptr<T>>,它确保所有对shared_ptr的操作都是线程安全的。

使用这种新特性的主要优点包括:

·原子性操作:对shared_ptr的引用计数的增加是原子的,确保在并发环境中正确管理资源。

·操作序列化:对shared_ptr的引用计数的减少操作虽然在原子操作之后进行,但是它的执行顺序是确定的,这有助于防止潜在的竞争条件。

·资源的安全释放:相关的删除和内存释放操作在更新后按顺序执行,虽然不是原子操作的一部分,但这确保了资源释放的正确性。

此外,值得一提的是,shared_ptr的控制块设计本身就是线程安全的。这意味着即使多个线程同时操作不同的shared_ptr实例,只要它们共享的控制块相同,这些操作本身就不会引起线程安全问题。std::atomic<std::shared_ptr<T>>的引入提供了更高级别的原子性和一致性保障,使得并发编程更加安全和高效,是C++20中对现代并发编程支持的重要扩展。

2.实现机制
1)std::unique_ptr的实现示例

下面将探讨std::unique_ptr的实现细节,通过一个简化的版本来展示它的核心技术要点。

在这个unique_ptr实现中,可以看到以下关键技术点:

·资源独占管理:通过删除拷贝构造函数和拷贝赋值运算符,确保unique_ptr不能被复制,从而实现资源的独占管理。

·自动资源释放:在析构函数中自动删除所管理的资源,实现资源的自动释放,防止内存泄漏。

·移动语义支持:通过实现移动构造函数和移动赋值运算符,unique_ptr可以将资源所有权从一个对象转移到另一个对象,同时确保资源不会被重复释放或泄漏。

·资源访问:通过get、operator*和operator->成员函数,允许对所管理的资源进行访问。

·资源释放与替换:release和reset成员函数提供了控制或替换所管理资源的能力,进一步增强了unique_ptr的灵活性和控制力。

通过这个简化的实现,我们可以清晰地看到std::unique_ptr如何有效地封装资源管理的责任,确保资源的正确释放,并提供了资源独占和转移的能力,这些都是现代C++资源管理的核心要求。

2)std::shared_ptr和std::weak_ptr的实现示例

与std::unique_ptr不同,std::shared_ptr支持多个指针实例共享对同一资源的所有权。下面将通过一个示例来展示std::shared_ptr和std::weak_ptr的实现及其交互。这两种智能指针共同协作,提供了一个强大的框架来处理动态分配的资源,同时避免常见的内存管理错误,如资源泄漏和循环引用。std::shared_ptr负责管理资源的生命周期,通过引用计数来确保资源在多个所有者之间共享时能够被正确管理和释放。相对地,std::weak_ptr提供了一种方法来观察std::shared_ptr管理的资源,而不延长其生命周期。

本示例详细展示了std::shared_ptr和std::weak_ptr在现代C++中的应用,以及它们如何共同作用于资源管理:

·强引用与弱引用的维护:std::shared_ptr通过引用计数机制维护资源的强引用,确保资源在多个所有者之间的正确共享和最终释放。与之相对的std::weak_ptr,通过监控但不增加std::shared_ptr的引用计数,提供了一种资源状态的观察方式,而不影响其生命周期。

·资源共享与自动释放:当最后一个std::shared_ptr实例被销毁时,它会自动释放所管理的资源,这防止了内存泄漏并简化了资源管理。这种自动管理是现代C++资源管理的核心特性。

·复制与移动操作:拷贝构造函数和拷贝赋值运算符增加了引用计数,支持资源的安全共享;移动构造函数和移动赋值运算符则转移了资源的所有权,这提高了效率并避免了不必要的计数增减。

·解决循环引用问题:std::weak_ptr的设计允许程序员安全地引用由std::shared_ptr管理的对象,同时避免了循环引用导致的内存泄漏问题。在复杂对象关系中,weak_ptr是解决资源管理问题的关键工具。

2.6.2 智能指针的使用技巧:优化资源管理的策略

1.适时使用std::weak_ptr避免循环引用

在C++中,管理动态内存时,经常使用智能指针来简化内存管理。其中,std::shared_ptr(共享指针)可以跟踪有多少个shared_ptr实例与一个特定资源相关联,并且只在没有shared_ptr指向这个资源时,资源才会被自动释放。然而,当两个shared_ptr相互引用时,它们之间就形成了循环引用,导致资源永远不会被释放,这就像人际关系中的依赖一样,如果没有外界介入,很难自行打破这种状态。

在这种情况下,std::weak_ptr(弱指针)显得尤为重要。使用weak_ptr可以观察资源,但不会增加资源的引用计数,从而避免了循环引用的问题。

举个例子,如果有两个类A和B,它们互相包含对方的shared_ptr,就很容易形成循环引用。改用weak_ptr后,即便其中一个类的实例被销毁,相关的资源也能正确释放,因为weak_ptr不会增加引用计数,从而避免了循环引用的问题。

#include <memory>

class B; //前置声明

class A {
public:
    std::shared_ptr<B> bPtr;
   ~A() {
        //资源清理

    }

};

class B {
public:
    std::weak_ptr<A> aPtr;  //使用weak_ptr替代shared_ptr
   ~B() {
        //资源清理

    }

};

void test() {
    auto a=std::make_shared<A>();
    auto b=std::make_shared<B>();
    a->bPtr=b;
    b->aPtr=a;

}

在这个例子中,当类A或类B的实例被销毁时,由于类B中的aPtr是weak_ptr,因此不会阻止类A实例的销毁。这就像在人际关系中,当一方有足够的独立性时,即使另一方消失或离开,前者仍能继续正常地生活或前行。

在多线程环境中,这种机制尤为重要,因为资源的生命周期管理需要更加谨慎。如果不适当管理,很容易因为对象生命周期的错误管理而导致竞态条件或死锁。

2.使用std::make_shared与std::make_unique的优势

在C++中,std::make_shared(创建共享指针)和std::make_unique(创建独占指针)是用于生成智能指针的工厂函数,它们分别对应于std::shared_ptr和std::unique_ptr。这些工厂函数不仅简化了智能指针的创建过程,而且提供了比直接使用智能指针构造函数更多的好处,正如在团队中有一个协调者可以使工作更加高效和有序。

使用std::make_shared和std::make_unique的主要优势之一是提高了内存使用效率。例如,std::make_shared可以在单个内存分配中同时为对象和其控制块分配内存,而直接使用std::shared_ptr构造函数则需要两次内存分配。这种优化类似于在日常生活中进行批量采购,可以减少成本和提高效率。

此外,使用这些工厂函数还有助于减少代码中的错误。它们通过避免显式使用new操作符,减少了内存泄漏的风险。这就像在生活中使用自动化工具来避免疏忽造成的错误。

下面通过一个例子来具体看看这些优势:

#include <memory>

class MyClass {
public:
    MyClass() {
        //构造函数逻辑
    }
   ~MyClass() {
        //析构函数逻辑

    }

};

void test() {
    //使用make_shared创建智能指针
    auto sharedPtr=std::make_shared<MyClass>();

    //使用make_unique创建智能指针
    auto uniquePtr=std::make_unique<MyClass>();

}

在这个例子中,std::make_shared和std::make_unique分别用于创建MyClass的共享和独占智能指针。通过这种方式,可以确保资源的安全管理和高效使用,正如一个精心设计的系统可以自动处理和优化其组成部分一样。

在多线程环境下,使用std::make_shared和std::make_unique尤为重要。因为它们通过减少内存分配次数和避免显式的new操作,降低了线程之间竞争和同步的复杂性,从而提高了性能和可靠性。

因此,std::make_shared和std::make_unique不仅体现了C++的设计哲学,即让资源管理更简单、安全,而且还能有效提升程序的性能和可维护性,这些都是在构建高效且可靠的系统时必须考虑的要素。

3.转移智能指针所有权

在C++中,智能指针的所有权转移是一个重要的概念,它允许一个智能指针将管理对象的所有权传递给另一个智能指针。这种机制在实现资源管理和线程间通信时尤为重要,就像在接力赛中,一名运动员将接力棒顺利传递给下一名运动员一样。

std::unique_ptr(独占指针)的所有权转移是通过移动构造函数和移动赋值运算符实现的,因为独占指针不允许复制,确保了资源的独占性。当我们使用std::move函数时,就像在生活中有意识地决定将一个任务或责任从一个人转移到另一个人,保证了任务的连续性和资源的有效利用。

让我们通过代码来理解这一点:

#include <memory>

class MyClass {
public:
    MyClass() {
        //构造函数逻辑
    }
   ~MyClass() {
        //析构函数逻辑

    }

};

void transferOwnership() {
    std::unique_ptr<MyClass> originalPtr=std::make_unique<MyClass>();
    std::unique_ptr<MyClass> newPtr=std::move(originalPtr); //转移所有权

    //此时,originalPtr为空,所有权已经转移到newPtr

}

在这个例子中,originalPtr最初拥有一个MyClass的实例,通过使用std::move(originalPtr),MyClass实例的所有权从originalPtr转移到了newPtr。在这个过程中,originalPtr变为空,而newPtr成为新的所有者。

对于std::shared_ptr(共享指针),所有权的概念略有不同,因为它允许多个指针共享对同一个资源的所有权。然而,在这种情况下,我们可以改变某个shared_ptr所指向的对象,从而在共享指针之间转移资源的“重点关注”。这类似于团队合作中角色和任务的动态调整,以适应项目的发展。

所有权的转移不仅关系到资源的有效管理,还涉及程序设计中的责任界定。在多线程环境中,正确管理智能指针的所有权转移尤为关键,它可以避免资源竞争和死锁,确保程序的稳定运行。这就如同在团队管理中,明确每个成员的职责和任务范围,能够使团队运作更加高效和协调。

通过智能指针的所有权转移,C++提供了一种强大的机制来控制和管理对象的生命周期,这与C++的设计哲学——提供既强大又灵活的资源管理工具——是完全一致的。这种机制使得程序员能够编写出既高效又可维护的代码,优化软件的整体性能和稳定性。

4.智能指针与原生指针的交互

智能指针与原生指针(raw pointer)的交互是C++程序设计中一个细致而重要的部分,它就像在两个不同文化背景的人之间建立有效沟通一样,需要明确界限和相互理解。

原生指针在C++中的使用历史悠久,但它们不自动管理内存,这就要求程序员手动释放分配的内存,容易导致内存泄漏或野指针。智能指针的出现,尤其是std::shared_ptr和std::unique_ptr,通过自动管理内存生命周期,大大减轻了这一负担。然而,在智能指针和原生指针之间需要建立一个清晰的界限,以确保程序的健全性和效率。

当需要将原生指针转换为智能指针时,可以使用std::make_shared或std::make_unique来创建智能指针。这种转换类似于将一项任务从个人管理转移到一个团队管理,从而提高了管理效率和可靠性。然而,直接将原生指针赋给智能指针构造函数需要谨慎,因为如果原生指针也在其他地方被释放或管理,可能会导致重复释放同一资源。例如:

MyClass* rawPtr=new MyClass();
std::shared_ptr<MyClass> smartPtr=std::make_shared<MyClass>(*rawPtr);

在上述代码中,通过rawPtr创建了一个MyClass实例的智能指针smartPtr。但是,这样做并不会接管rawPtr本身的内存管理责任,而是创建了一个新的MyClass实例的拷贝。如果想要智能指针接管原生指针的管理权,应该直接使用智能指针的构造函数:

std::shared_ptr<MyClass> smartPtr(rawPtr);

但这种方式需要确保原生指针不会在其他地方被错误地管理(如重复释放)。因此,推荐在创建原生指针的同时就将其转换为智能指针,避免潜在的内存管理错误。

在某些情况下,可能需要从智能指针获取原生指针,比如调用某些旧的C API。这可以通过get方法实现:

std::shared_ptr<MyClass> smartPtr=std::make_shared<MyClass>();
MyClass* rawPtr=smartPtr.get();

在这种情况下,虽然获得了原生指针,但智能指针依然保有对象的所有权。这就要求在使用原生指针时必须确保智能指针仍然存在,以防对象被提前释放。

智能指针和原生指针的交互需要谨慎处理,以保持资源管理的准确性和程序的稳定性。这种交互类似于在不同管理系统之间进行任务转移,需要确保过程中的每一步都是清晰和可控的。正确地管理智能指针和原生指针之间的关系,有助于避免内存泄漏和其他资源管理问题,从而提高程序的健壮性和可维护性。

5.智能指针在多线程环境下的使用注意事项和性能考量

在多线程环境中使用智能指针时,关键是要考虑线程安全和性能影响。尤其是std::shared_ptr,其内部的引用计数机制虽然是线程安全的,但频繁操作(如创建和销毁)会涉及原子操作,可能成为性能瓶颈。C++20引入的std::atomic<std::shared_ptr<T>>提供了一种更安全的并发访问机制,通过确保引用计数的完全原子操作,避免了数据竞争和竞争条件。

1)线程安全性

std::shared_ptr通过原子操作维护引用计数,确保多线程环境下的基本线程安全。然而,指向的对象的线程安全性取决于该对象自身。对于需要更高级别的线程安全保障,std::atomic<std::shared_ptr<T>>是更合适的选择,它支持原子性操作共享指针,优化并发访问的性能和安全性。

2)性能考量

尽管std::shared_ptr提供便利的线程安全特性,但其原子操作引用计数可能导致性能下降,特别是在高并发场景中。优化策略包括减少std::shared_ptr对象的不必要拷贝,使用引用传递,或考虑使用std::weak_ptr来减轻引用计数的负担。在对象不需要共享时,std::unique_ptr提供了更高效的选择,无引用计数开销。

3)使用建议

对于智能指针的使用,有以下两点建议:

·使用std::atomic<std::shared_ptr<T>>以支持更复杂的并发模式,如多个生产者和消费者同时修改共享指针。

·优化智能指针的使用,如通过传递引用到线程函数,以减少不必要的拷贝和引用计数更新。通过以上策略,开发者可以有效管理智能指针在多线程程序中的使用,确保应用的高效和稳定性。这些实践不仅有助于避免常见的并发问题,还可以提升整体性能。

6.掌握智能指针的自我引用:shared_from_this和weak_from_this
1)继承和使用std::enable_shared_from_this

shared_from_this是C++11中引入的功能,允许对象在继承了std::enable_shared_from_this的情况下,安全地生成自身的std::shared_ptr实例,而不会创建新的控制块(引用计数块)。这样可以避免悬挂指针的问题,特别是在对象的成员函数中使用时,可以确保对象在使用期间不被销毁。

下面是一个简单的例子:

#include <iostream>
#include <memory>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    void show() {
        std::cout << "MyClass instance" << std::endl;

    }

    std::shared_ptr<MyClass> getShared() {
        return shared_from_this();

    }

};

int main() {
    std::shared_ptr<MyClass> ptr=std::make_shared<MyClass>();
    ptr->show();
    std::shared_ptr<MyClass> anotherPtr=ptr->getShared();
    //'ptr' and 'anotherPtr' now share ownership of the same object

}

在C++11及其之后的版本中,为了在类的内部安全地使用shared_from_this()方法,类必须继承自std::enable_shared_from_this<T>。这是因为shared_from_this() std::enable_shared_from_this<T>的成员函数,只有继承了这个基类的对象才具备调用它的能力。这种设计的原因是std::enable_shared_from_this<T>内部维护了一个std::weak_ptr<T>。当第一个std::shared_ptr<T>开始管理该对象时,weak_ptr被初始化。之后,当shared_from_this()被调用时,它将基于这个已经存在的weak_ptr返回一个新的std::shared_ptr<T>,这个新的shared_ptr与原有的shared_ptr共享对象的所有权。这样就避免了在对象内部直接创建新的std::shared_ptr实例,这种直接创建可能导致独立的所有权块的形成,增加了资源释放错误的风险。

(1)使用注意事项

·构造函数中禁用:在对象的构造函数中使用shared_from_this是错误的,因为此时还没有std::shared_ptr实例管理该对象。

·安全调用条件:只有当至少有一个std::shared_ptr实例正在管理该对象时,调用shared_from_this才是安全的。在任何std::shared_ptr管理该对象之前,shared_from_this将无法正确工作并可能抛出异常。

(2)继承与构造行为

继承std::enable_shared_from_this并不改变如何构造对象,我们仍需提供适当的构造函数,特别是在默认构造函数不适用的情况下。此外,虽然std::enable_shared_from_this是一个基类,但继承它并不意味着基类和派生类之间共享对象所有权。相反,这种继承关系允许派生类在必要时通过shared_from_this()安全地生成一个新的std::shared_ptr实例,这个新实例将与已经存在的、管理同一对象的shared_ptr共享所有权。

(3)C++17之前获取weak_ptr的做法

在C++17之前,如果需要在类的内部获取一个指向自己的weak_ptr,必须先调用shared_from_this()来获得一个shared_ptr,然后从这个shared_ptr创建一个weak_ptr。这样的操作是安全的,但它稍显间接和不便。

例如,在C++17之前,可能会这样写:

class Listener : public std::enable_shared_from_this<Listener> {
public:
    std::weak_ptr<Listener> getWeakPtr() {
        return shared_from_this();
    }
    //...

};

在这个例子中,getWeakPtr方法首先调用shared_from_this()来获取一个shared_ptr,然后自动将其转换为weak_ptr。

而在C++17中,enable_shared_from_this类模板被增强,包括了一个weak_from_this方法,直接返回一个weak_ptr,这使得代码更直接和简洁。这个改进减少了创建临时shared_ptr的场景,使得代码更加高效和易于理解。

2)C++17中的weak_from_this

在C++17更新之前,std::enable_shared_from_this缺少直接获取std::weak_ptr的方法。C++17通过引入weak_from_this使得std::enable_shared_from_this在处理复杂对象关系和资源管理时变得更为灵活和安全。

该函数自C++17起提供了两个版本:

这两个函数返回一个std::weak_ptr<T>,该智能指针追踪所有指向*this的std::shared_ptr实例。

3)std::enable_shared_from_this和weak_from_this的使用示例

下面是一个使用std::enable_shared_from_this和weak_from_this的C++示例。这个例子模拟了一个简单的事件监听器系统,其中监听器可以注册到事件发生器上。使用std::weak_ptr可以防止循环引用,同时确保在尝试通知监听器时,监听器仍然存在。

在这个示例中,EventGenerator类有一个方法registerListener,它接收一个指向Listener的std::shared_ptr并将其存储为std::weak_ptr。这样做的好处是,EventGenerator不会增加Listener实例的引用计数,从而防止循环引用的问题。当EventGenerator需要通知监听器时,它会尝试通过调用std::weak_ptr::lock来获取一个std::shared_ptr,如果相关Listener已经被销毁,则lock会失败,这样就避免了访问悬挂指针的风险。

这个示例展示了std::enable_shared_from_this和weak_from_this在复杂的对象关系和生命周期管理中的应用,特别是在事件监听系统这类场景下的有效性。

4)小结

shared_from_this和weak_from_this的使用场景和目的有所不同。

(1)shared_from_this

用途: shared_from_this用于在类的成员函数内部安全地获取一个指向当前对象的std::shared_ptr。这适用于需要确保当前对象在函数执行期间保持存活的场景。

C++11及以后: 这个方法自C++11 引入,适用于所有继承自std::enable_shared_from_this的类。

场景: 例如,当一个类的成员函数需要将this对象作为shared_ptr传递给其他函数或存储它时,使用shared_from_this。

(2)weak_from_this

用途: C++17新增的weak_from_this方法返回一个std::weak_ptr,用于创建一个不增加引用计数的指针,这对于避免循环引用特别有用。

C++17新增: 这是C++17新增的功能,用于获取一个weak_ptr,从而可以在不创建额外shared_ptr(和不增加引用计数)的情况下观察对象。

场景: 当需要引用一个对象,但又不想拥有它(即不想增加引用计数),以避免循环引用或其他所有权问题时,使用weak_from_this。

综上所述,shared_from_this和weak_from_this都是在特定场景下的解决方案。

在C++17之后,我们拥有了更多的选择:如果需要共享所有权并确保对象在使用期间保持存活,可以使用shared_from_this;如果需要引用对象但不取得所有权,以避免循环引用或其他问题,可以使用weak_from_this。 HZ7NjKhyeynt4QHsyQ0EjV+1AejJ6JBkkm6Fyz5c5AZHwqHGKJgd6i5qlIcP6fT2

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开