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

C++11中智能指针的使用(shared_ptr、unique_ptr、weak_ptr)

C++11中智能指针的使用(shared_ptr、unique_ptr、weak_ptr)

一、shared_ptr原理

shared_ptr 是另一种智能指针,用于实现多个 shared_ptr 实例共享同一个对象的所有权。它通过内部的控制块(通常是一个包含计数器和指向对象的指针的结构)来管理对象的生命周期。每当一个新的 shared_ptr 被创建并指向对象时,控制块中的计数器就会递增;每当一个 shared_ptr 被销毁或重置时,计数器就会递减。当计数器减至零时,对象被删除。

1.std::shared_ptr的原理

std::shared_ptr 的核心原理是引用计数。它通过一个**控制块(Control Block)**来管理对象的生命周期。控制块记录了以下信息:

1.1 引用计数(Strong Count)

表示当前有多少个 std::shared_ptr 正在管理同一个对象。

当一个 std::shared_ptr 被复制时,引用计数加1。

当一个 std::shared_ptr 被销毁时,引用计数减1。

当引用计数变为0时,对象会被自动释放。

1.2 弱引用计数(Weak Count)

用于支持 std::weak_ptr,表示有多少个 std::weak_ptr 正在观察这个对象。

当弱引用计数变为0时,控制块本身也会被销毁。

1.3 删除器(Deleter)

一个函数对象,用于在对象被销毁时执行清理操作(如释放内存)。

1.4 分配器(Allocator)

用于分配和释放内存。

1.1 控制块的结构

控制块通常是一个独立的结构体,与 std::shared_ptrstd::weak_ptr 共享。它的结构大致如下:

struct ControlBlock {
    int* ptr;          // 指向被管理的对象
    unsigned* strong;  // 引用计数(Strong Count)
    unsigned* weak;    // 弱引用计数(Weak Count)
    Deleter deleter;   // 删除器
};
1.2 生命周期管理
  • 当创建一个新的 std::shared_ptr 时,控制块会被初始化,引用计数设置为1。

  • 当一个 std::shared_ptr 被复制时,引用计数加1。

  • 当一个 std::shared_ptr 被销毁时,引用计数减1。如果引用计数变为0,控制块会调用删除器来释放对象。

  • 当最后一个 std::weak_ptr 被销毁时,控制块本身也会被销毁。

1.3 引用计数的线程安全性

std::shared_ptr 的引用计数操作是线程安全的。它使用原子操作来保证在多线程环境下引用计数的正确性。

2、std::shared_ptr的用法

2.1 创建 std::shared_ptr
(1)使用 std::make_shared

推荐使用 std::make_shared 来创建 std::shared_ptr,因为它更高效且安全。

#include <iostream>
#include<memory>
using namespace std;

int main()
{
    // 创建一个shared_ptr
    shared_ptr<int> ptr = make_shared<int>(20);
    cout << "Value = "<< *ptr << endl;
}

std::make_shared 会同时分配对象和控制块的内存,减少了内存分配的次数。

(2)直接构造

也可以通过构造函数直接创建,但需要小心避免悬挂指针。

 int* ptr2 = new int(10);
 shared_ptr<int> ptr3(ptr2);
(3)从其他智能指针转换

std::shared_ptr 可以从 std::unique_ptr 或其他 std::shared_ptr 转换而来。

std::shared_ptr<int> ptr3 = std::make_shared<int>(42);
std::shared_ptr<int> ptr4 = ptr3; // 复制构造,引用计数增加
2.2 引用计数机制

std::shared_ptr 的核心是引用计数。当一个 std::shared_ptr 被复制时,引用计数会增加;当一个 std::shared_ptr 被销毁时,引用计数会减少。当引用计数为零时,它所管理的对象会被自动释放。

std::shared_ptr<int> ptr5 = std::make_shared<int>(100);
{
    std::shared_ptr<int> ptr6 = ptr5; // 引用计数 +1
    std::cout << "Value: " << *ptr6 << std::endl;
    std::cout << "ptr5 Use_Count: " << ptr5.use_count() << std::endl;
    std::cout << "ptr6 Use_Count: " << ptr6.use_count() << std::endl;

} // ptr6 超出作用域,引用计数 -1
std::cout << "Value: " << *ptr5 << std::endl; // 仍然可以访问
std::cout << "Use_Count: " << ptr5.use_count() << std::endl;

代码运行结果:
在这里插入图片描述

在C++中通过 std::shared_ptr 的成员函数 use_count() 来获取当前 shared_ptr 的引用计数。这个函数返回一个 std::size_t 类型的值,表示当前有多少个 std::shared_ptr 共享同一个控制块。

2.3 使用自定义删除器

如果需要对对象进行特殊处理(如释放资源或调用特定函数),可以为 std::shared_ptr 提供自定义删除器。

std::shared_ptr<int> ptr7(new int(30), [](int* p) {
    cout << "Custom Deleter Call" << endl;
    delete p;
    });

在这里插入图片描述

程序结束,调用客户自定义的删除器。

2.4 使用 std::weak_ptr 避免循环引用

std::shared_ptr 可能会导致循环引用问题,从而无法正确释放资源。通过 std::weak_ptr 可以解决这个问题。

class B;

class A
{
public:
    shared_ptr<B> m_ptrB;
    ~A() { cout << "A 析构" << endl; };
};

class B
{
public:
    shared_ptr<A> m_ptrA;
    ~B() { cout << "B 析构" << endl; };
};

int main()
{
    shared_ptr<A> ptrA = make_shared<A>();
    shared_ptr<B> ptrB = make_shared<B>();

    ptrA->m_ptrB = ptrB;
    ptrB->m_ptrA = ptrA;
}

上述代码运行时不会调用析构函数。

在这里插入图片描述

解决办法:将B类中的shared_ptr改为weak_ptr后,程序运行结果:

在这里插入图片描述

2.5 其他操作
  • reset():释放当前管理的对象,并可选地绑定到新的对象。

  • use_count():返回当前对象的引用计数。

  • get():返回底层裸指针(不推荐直接使用,仅在必要时)。

std::shared_ptr<int> ptr8 = std::make_shared<int>(200);
std::cout << "Use count: " << ptr8.use_count() << std::endl; // 输出引用计数
ptr8.reset(); // 释放对象

3、注意事项

  1. 循环引用问题

    • 如果两个或多个 std::shared_ptr 相互引用,会导致引用计数永远不会变为0,从而无法释放对象。

    • 使用 std::weak_ptr 可以解决循环引用问题。

  2. 不要直接操作底层指针

    • 尽量避免使用 get() 获取底层指针,因为这可能导致悬挂指针或内存泄漏。

    • 如果需要操作底层指针,建议使用 std::weak_ptr 来确保对象仍然有效。

  3. 性能开销

    • std::shared_ptr 的引用计数操作是线程安全的,但会带来一定的性能开销。

    • 如果不需要线程安全,可以考虑使用 std::unique_ptr

  4. 优先使用 std::make_shared

    std::make_shared 会同时分配对象和控制块的内存,减少了内存分配的次数,性能更好。

4、注意事项

std::shared_ptr 是一种非常强大的智能指针工具,适用于需要共享所有权的场景。它通过引用计数自动管理内存,减少了内存泄漏和悬挂指针的风险。但在使用时需要注意以下几点:

  • 尽量避免循环引用,必要时使用 std::weak_ptr

  • 不要直接操作底层指针,除非绝对必要。

  • 优先使用 std::make_shared 创建 std::shared_ptr,因为它更高效。

二、unique_ptr原理

unique_ptr是独占式的智能指针,每一次只会有一个指针指向其给定的对象。当unique_ptr离开其作用域时,其所指的对象会被自动删除,并且该对象拥有的任何资源都会被释放。

std::unique_ptr 是 C++11 引入的一种智能指针,用于管理动态分配的资源(如通过 new 分配的内存)。它的核心原理是通过独占所有权语义来自动管理资源的生命周期,确保资源在合适的时机被释放,从而避免内存泄漏和野指针问题。

以下是 std::unique_ptr 的工作原理和关键特性:

1. 独占所有权

std::unique_ptr 采用独占所有权机制,即同一时间只能有一个 unique_ptr 指向某个资源。这意味着:

  • 不能复制unique_ptr 不能被复制(即没有拷贝构造函数和拷贝赋值运算符),因为复制会导致多个指针指向同一资源,从而破坏所有权的独占性。

    微信截图_20250315111348

  • 可以移动unique_ptr 支持移动语义(通过移动构造函数和移动赋值运算符),允许将资源的所有权从一个 unique_ptr 转移到另一个 unique_ptr。移动操作会将原指针置为 nullptr,确保资源的唯一所有权。

    微信截图_20250315111447

2. 自动释放资源

std::unique_ptr 在以下情况下会自动释放其管理的资源:

  • 析构函数:当 unique_ptr 被销毁(如超出作用域、对象析构)时,它会调用资源的析构函数(如 delete)来释放资源。

  • 重置:通过调用 reset() 方法,可以手动释放当前资源,并可选择分配新的资源。

    微信截图_20250315111954

3. 定制删除器

std::unique_ptr 允许用户自定义删除器(deleter),这使得它不仅可以管理动态分配的内存,还可以管理其他类型的资源(如文件句柄、网络连接等)。删除器是一个可调用对象,用于定义资源的释放方式。默认情况下,unique_ptr 使用 delete 作为删除器,但用户可以通过模板参数或构造函数传递自定义删除器。

struct FileDeleter
{
    void operator()(FILE* file)
    {
        fclose(file);
        cout << "Custom Deleter called" << endl;
    }
};


unique_ptr<FILE,FileDeleter> file(fopen("1.txt","r"));
file.reset(); // 调用自定义删除器关闭文件

4. 实现原理

std::unique_ptr 的实现基于模板和智能指针的底层机制:

模板参数std::unique_ptr 是一个模板类,通常有两个模板参数:

T:指针指向的类型。

Deleter:删除器类型(默认为 std::default_delete<T>)。

内部存储unique_ptr 内部存储一个裸指针(T*)和一个删除器对象。它通过操作这个裸指针来管理资源,并在需要时调用删除器释放资源。

移动语义unique_ptr 的移动构造函数和移动赋值运算符通过交换内部指针和删除器来实现资源的转移,确保所有权的唯一性。

5. 使用示例

以下是一个简单的 std::unique_ptr 使用示例:

#include <iostream>
#include <memory>

struct Foo // object to manage
{
    Foo() { std::cout << "Foo...\n"; }
    ~Foo() { std::cout << "~Foo...\n"; }
};

struct D // deleter
{
    void operator() (Foo* p)
    {
        std::cout << "Calling delete for Foo object... \n";
        delete p;
    }
};

int main()
{
    std::cout << "Creating new Foo...\n";
    std::unique_ptr<Foo, D> up(new Foo(), D()); // up owns the Foo pointer (deleter D)

    std::cout << "Replace owned Foo with a new Foo...\n";
    up.reset(new Foo());  // calls deleter for the old one

    std::cout << "Release and delete the owned Foo...\n";
    up.reset(nullptr);      
}

代码运行结果:

微信截图_20250315122904

6. 优点

自动管理资源:避免手动调用 delete,减少内存泄漏风险。

独占所有权:确保同一时间只有一个指针管理资源,避免野指针问题。

支持自定义删除器:可以管理除动态内存之外的其他资源。

轻量级std::unique_ptr 通常只有裸指针大小,性能开销极小。

7.缺点

  • 不能复制:由于独占所有权,unique_ptr 不能被复制,这在某些场景下可能需要额外的逻辑来处理。
  • 依赖移动语义:需要 C++11 或更高版本支持,因为其依赖于移动构造函数和移动赋值运算符。

总之,std::unique_ptr 是一种非常强大且轻量级的智能指针,适用于大多数需要管理动态资源的场景。

三、weak_ptr原理

在C++中,weak_ptr是一种智能指针,用于解决shared_ptr的循环引用问题。它不拥有对象的所有权,而是观察shared_ptr管理的对象,避免增加引用计数。以下是weak_ptr的原理和用法详解:

1. weak_ptr的核心原理

不增加引用计数weak_ptr指向由shared_ptr管理的对象,但不会增加其引用计数。当最后一个shared_ptr被销毁时,对象仍会被释放,即使有weak_ptr存在。

观察者模式weak_ptr只是"观察"资源,不控制生命周期。若要访问资源,需临时转换为shared_ptr(通过lock()方法)。

解决循环引用:在相互持有shared_ptr的场景中(如双向链表、父-子对象),使用weak_ptr打破循环,防止内存泄漏.

1.1 std::shared_ptr 的关系
  • std::weak_ptr 是基于 std::shared_ptr 的。它不会增加对象的引用计数,但会与 std::shared_ptr 共享对象的控制块(control block)。控制块中包含了对象的引用计数和弱引用计数。

  • 引用计数(use_count):记录有多少个 std::shared_ptr 指向对象。当引用计数为 0 时,对象会被销毁。

  • 弱引用计数(weak_count):记录有多少个 std::weak_ptr 指向对象。弱引用计数不会影响对象的生命周期,但当对象被销毁时,所有指向该对象的 std::weak_ptr 会失效。

1.2 实现原理
  • 当创建一个 std::weak_ptr 时,它会从一个 std::shared_ptr 或另一个 std::weak_ptr 中获取控制块的指针,并增加弱引用计数。

  • 当使用 std::weak_ptrlock() 方法时,它会检查控制块中的对象是否仍然存在。如果对象存在,它会返回一个指向该对象的 std::shared_ptr,并增加引用计数;如果对象已经被销毁,则返回一个空的 std::shared_ptr

  • 当一个 std::weak_ptr 被销毁时,它会减少弱引用计数。当弱引用计数和引用计数都为 0 时,控制块也会被销毁。

2. 基本用法

2.1** 创建weak_ptr**

必须从shared_ptr或另一个weak_ptr构造.

std::shared_ptr 创建:从 std::shared_ptr 创建:

std::shared_ptr<int> ptr = std::make_shared<int>(10);
std::weak_ptr<int> w_ptr(ptr); // 从shared_ptr中构造

从另一个 std::weak_ptr 创建:

 std::weak_ptr<int> w_ptr2(w_ptr);
2.2** 检查对象是否仍然存在**
  • 使用 expired() 方法检查对象是否已经被销毁:
 if (w_ptr2.expired())
 {
     std::cout << "Object has been destroyed." << std::endl; 
 }
 else
 {
     std::cout << "Object still exists." << std::endl;
 }
2.3获取对象的 std::shared_ptr
  • 使用 lock() 方法获取对象的 std::shared_ptr
 std::shared_ptr<int> shared_from_weak = w_ptr2.lock();
 if (shared_from_weak) {
     std::cout << "Accessing object: " << *shared_from_weak << std::endl;
 }
 else {
     std::cout << "Object has been destroyed." << std::endl;
 }

如果对象仍然存在,lock() 方法会返回一个指向该对象的 std::shared_ptr;如果对象已经被销毁,则返回一个空的 std::shared_ptr

3、std::weak_ptr 的使用场景

3.1解决循环引用问题
  • 在使用 std::shared_ptr 时,可能会出现循环引用的情况,导致对象无法被正确销毁。例如,两个对象互相持有对方的 std::shared_ptr,它们的引用计数永远不会为 0。

  • 通过将其中一个引用改为 std::weak_ptr,可以打破循环引用。例如:

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
};

class B {
public:
    std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;
    return 0;
}

在这个例子中,B 持有 Astd::weak_ptr,避免了循环引用。

3.2观察但不拥有对象

当一个对象需要被多个组件观察,但这些组件不需要拥有对象时,可以使用 std::weak_ptr。例如,一个观察者模式中,观察者可以持有被观察对象的 std::weak_ptr,这样即使观察者仍然存在,也不会阻止被观察对象的销毁。

3.3缓存场景

在某些缓存场景中,可以使用 std::weak_ptr 来存储对对象的弱引用。当对象被销毁时,缓存中的 std::weak_ptr 会自动失效,避免了对已销毁对象的访问。

4、注意事项

  1. 线程安全

    • std::weak_ptr 的操作(如 lock()expired() 等)是线程安全的,因为它们都是通过控制块中的原子操作来实现的。
  2. 生命周期管理

    • 使用 std::weak_ptr 时,需要注意对象的生命周期。虽然 std::weak_ptr 不会阻止对象的销毁,但在访问对象时,必须确保对象仍然存在。
  3. 性能开销

    • std::weak_ptr 的使用会带来一定的性能开销,因为它需要维护弱引用计数。在性能敏感的场景中,需要权衡使用 std::weak_ptr 的利弊。

总之,std::weak_ptr 是一种非常有用的智能指针,它通过与 std::shared_ptr 共享控制块,提供了对对象的弱引用。它可以用于解决循环引用问题、观察对象以及缓存场景等。在使用时,需要注意对象的生命周期和性能开销。

相关文章:

  • FPGA_YOLO(三)
  • Python使用SVC算法解决乳腺癌数据集分类问题——寻找最佳核函数
  • 【UEFI】关于Secure Boot
  • 2.3.5 覆盖率数据的合并
  • 【前端】使用 HTML、CSS 和 JavaScript 创建一个数字时钟和搜索功能的网页
  • 计算机二级:基础操作题
  • 大模型在支气管哮喘手术全流程风险预测与治疗方案制定中的应用研究
  • 【HTML 基础教程】HTML 编辑器
  • 【Java 优选算法】链表
  • C++:异常的深度解析
  • STM32学习笔记之常见外设汇总
  • 【QA】外观模式在Qt中有哪些应用?
  • 【C++】Chapter02 内存管理
  • 群体智能优化算法-多版本优化器(Multi-Verse Optimizer, MVO,含Matlab源代码)
  • (Arxiv-2025)MagicDistillation:用于大规模人像少步合成的弱到强视频蒸馏
  • notify_one() 会阻塞吗?
  • 5分钟快速上手Docker容器化部署:从零到实践
  • 紧凑交叉引用表
  • 使用XiaoESP32S3在Arduino环境中实现颜色识别
  • 安铂克科技APLC系列射频模拟信号发生器
  • “五一”假期首日:国铁南宁局发送旅客81.7万人次
  • 香港发生车祸致22人受伤,4人伤势严重
  • 净海护渔,中国海警局直属第一局开展伏季休渔普法宣传活动
  • 4月一二线城市新房价格环比上涨,沪杭涨幅居百城前列
  • 涉嫌严重违纪违法,57岁证监会副主席王建军被查
  • 迪卡侬回应出售中国业务30%股份传闻:始终扎根中国长期发展