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

shared_ptr 智能指针

shared_ptr 智能指针

文章目录

  • shared_ptr 智能指针
    • 为什么要引入智能指针
    • 智能指针的作用
    • C++ 中的智能指针种类
    • std::shared_ptr
      • 使用 `std::make_shared` 创建 `shared_ptr`(推荐方式)
      • 使用 `shared_ptr` 的拷贝或移动构造
      • 通过构造函数初始化
      • 通过reset方法初始化
      • 获取原始指针
    • 指定删除器
    • 使用 `std::shared_ptr` 的注意事项
      • 循环引用
      • 使用 `std::weak_ptr` 解决循环引用

为什么要引入智能指针

在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

智能指针的作用

  1. 自动管理动态内存:智能指针通过 RAII(资源获取即初始化)机制自动释放内存。RAII 是 C++ 的核心设计理念之一,资源的生命周期与对象的生命周期绑定,从而确保资源不会被泄漏。
  2. 避免内存泄漏:智能指针通过引用计数或其他机制,确保内存能够在不再使用时自动释放。
  3. 避免悬挂指针和野指针:智能指针能够在其不再使用时自动销毁,避免了手动删除后指针仍然存在的危险。
  4. 确保异常安全:智能指针通过作用域控制内存,确保即使发生异常,也能正确释放资源。
  5. 简化代码:相比传统指针手动管理内存,智能指针大大简化了内存管理代码,提升了程序的可维护性和可靠性。

C++ 中的智能指针种类

  1. std::unique_ptr
  • 用途:表示独占所有权的智能指针,意味着一个 unique_ptr 只能拥有一个对象的所有权。不能被复制,只能移动。
  • 特点
    • 独占所有权:同一个对象只能有一个 unique_ptr 管理,避免了多重释放的问题。
    • 不可拷贝:不能通过拷贝构造函数或赋值操作符来复制,确保了对象的唯一所有权。
    • 可以转移所有权:可以通过 std::moveunique_ptr 的所有权从一个指针转移到另一个。
  • 典型用法
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1);  // 转移所有权
  1. std::shared_ptr
  • 用途:表示共享所有权的智能指针,多个 shared_ptr 可以共同拥有同一个对象的所有权。
  • 特点
    • 引用计数shared_ptr 使用引用计数的机制来跟踪有多少个 shared_ptr 指向同一对象。当最后一个指向对象的 shared_ptr 被销毁时,内存会自动释放。
    • 可以拷贝和赋值shared_ptr 可以通过拷贝构造和赋值操作符进行拷贝,因此它适用于多个地方共享资源的情况。
    • 线程安全shared_ptr 在修改引用计数时是线程安全的,但对象本身的操作仍需要额外的同步机制。
  • 典型用法
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1;  // 引用计数增加,两个指针共享所有权
  1. std::weak_ptr
  • 用途:用于解决 shared_ptr 引用计数环的问题。weak_ptr 并不增加引用计数,只是观察 shared_ptr 指向的对象。
  • 特点
    • 不增加引用计数weak_ptr 不会增加 shared_ptr 的引用计数,因此不会影响对象的生命周期。
    • **可以转换为 **shared_ptr:如果对象仍然存在,weak_ptr 可以通过 lock() 方法转换为 shared_ptr,从而访问对象。
    • 防止循环引用:在 shared_ptr 之间形成循环引用时,weak_ptr 可以用来打破这种循环,避免内存泄漏。
  • 典型用法
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::weak_ptr<int> weakPtr = ptr1;  // 不增加引用计数
if (auto ptr2 = weakPtr.lock()) {   // 如果对象还存在,获得一个 shared_ptr
    // 使用 ptr2
}

std::shared_ptr

使用 std::make_shared 创建 shared_ptr(推荐方式)

std::make_shared 是创建 std::shared_ptr 的推荐方法,它同时分配 shared_ptr 和对象的内存,能够提高效率并减少内存分配次数。

使用std::make_shared()模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor\n"; }
    ~MyClass() { std::cout << "MyClass destructor\n"; }
    void say_hello() { std::cout << "Hello, World!\n"; }
};

int main() {
    // 使用 std::make_shared 创建 shared_ptr
    std::shared_ptr<MyClass> p1 = std::make_shared<MyClass>();

    p1->say_hello(); // 调用成员函数

    // 不需要手动释放内存,shared_ptr 会自动销毁对象
    return 0;
}
----- 输出 -----
MyClass constructor
Hello, World!
MyClass destructor

使用 shared_ptr 的拷贝或移动构造

如果已有一个 shared_ptr,可以通过拷贝或移动构造一个新的 shared_ptr,共享同一个对象。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor\n"; }
    ~MyClass() { std::cout << "MyClass destructor\n"; }
    void say_hello() { std::cout << "Hello, World!\n"; }
};

int main() {
    // 创建 shared_ptr
    std::shared_ptr<MyClass> p1 = std::make_shared<MyClass>();

    // 拷贝构造
    std::shared_ptr<MyClass> p2 = p1;

    // 移动构造
    std::shared_ptr<MyClass> p3 = std::move(p1);

    p2->say_hello();
    p3->say_hello(); // p1 已经为空,p3 拥有资源

    return 0;
}

通过构造函数初始化

// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    // 使用智能指针管理一块字符数组对应的堆内存
    shared_ptr<char> ptr2(new char[12]);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    // 创建智能指针对象, 不管理任何内存
    shared_ptr<int> ptr3;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    // 创建智能指针对象, 初始化为空
    shared_ptr<int> ptr4(nullptr);
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    return 0;
}
----- 输出:
ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 1
ptr3管理的内存引用计数: 0
ptr4管理的内存引用计数: 0

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数不会+1。另外,不要使用一个原始指针初始化多个shared_ptr。

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);      // error, 编译不会报错, 运行会出错

通过reset方法初始化

共享智能指针类提供的std::shared_ptr::reset方法函数原型如下:

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

  1. ptr:指向要取得所有权的对象的指针。调用此 reset 后,会接管这个裸指针的所有权,原本的管理对象会被删除。
  2. d: 删除器用于删除 ptr 指向的对象。允许你在重置时自定义对象的销毁方式。
  3. Alloc : 是一个自定义分配器,它控制内存的分配和释放,通常用于自定义内存管理场景。
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    shared_ptr<int> ptr2 = ptr1;
    shared_ptr<int> ptr3 = ptr1;
    shared_ptr<int> ptr4 = ptr1;
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    ptr4.reset();
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    shared_ptr<int> ptr5;
    ptr5.reset(new int(250));
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;

    return 0;
}

-----输出:
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4

ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0

ptr5管理的内存引用计数: 1

对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。

获取原始指针

通过智能指针可以管理一个普通变量或者对象的地址,此时原始地址就不可见了。当我们想要修改变量或者对象中的值的时候,就需要从智能指针对象中先取出数据的原始内存的地址再操作,解决方案是调用共享智能指针类提供的get()方法,其函数原型如下:

T* get() const noexcept;

指定删除器

std::shared_ptr 允许使用指定的删除器(Deleter)来定制对象销毁的方式。删除器是一个函数对象、函数指针或任何具有 operator() 的对象,可以在 shared_ptr 被销毁时,执行自定义的删除操作。

原型如下:

std::shared_ptr<T> ptr(p, Deleter deleter);
1 > p 是裸指针,shared_ptr 将负责管理它的生命周期。
2 > Deleter 是一个删除器,它在 shared_ptr 被销毁时被调用,用来销毁管理的对象。

使用:

#include <iostream>
#include <memory>

void custom_deleter(int* ptr) {
    std::cout << "Custom deleter deleting pointer: " << *ptr << std::endl;
    delete ptr;  // 使用 delete 进行销毁
}

int main() {
    // 创建一个 shared_ptr,传入裸指针和自定义删除器
    std::shared_ptr<int> ptr(new int(42), custom_deleter);

    // 使用 ptr 进行操作
    std::cout << "Shared pointer holds: " << *ptr << std::endl;

    /*
    // 使用 lambda 表达式作为删除器
    auto deleter = [](int* ptr) {
        std::cout << "Lambda deleter deleting pointer: " << *ptr << std::endl;
        delete ptr;
    };

    std::shared_ptr<int> ptr(new int(100), deleter);

    std::cout << "Shared pointer holds: " << *ptr << std::endl;
    */
    // 当 ptr 超出作用域时,custom_deleter 会被调用
    return 0;
}
-----  输出 ----
Shared pointer holds: 42
Custom deleter deleting pointer: 42

使用 std::shared_ptr 的注意事项

循环引用

std::shared_ptr 会通过引用计数来管理对象的生命周期,如果两个 shared_ptr 相互持有对方,形成循环引用,那么它们的引用计数永远不会减少,导致内存泄漏。

#include <iostream>
#include <memory>

class A; // 前向声明

class B {
public:
    std::shared_ptr<A> ptrA;
    B() { std::cout << "B created\n"; }
    ~B() { std::cout << "B destroyed\n"; }
};

class A {
public:
    std::shared_ptr<B> ptrB;
    A() { std::cout << "A created\n"; }
    ~A() { std::cout << "A destroyed\n"; }
};

int main() {
    std::shared_ptr<A> pA = std::make_shared<A>();
    std::shared_ptr<B> pB = std::make_shared<B>();

    pA->ptrB = pB;
    pB->ptrA = pA;

    // pA 和 pB 互相持有对方,导致循环引用,内存不会被释放
    return 0;
}

这个程序不会输出 “A destroyed” 和 “B destroyed”。这是因为 pA 和 pB 互相持有对方,引用计数永远不会降到 0,导致内存泄漏。

使用 std::weak_ptr 解决循环引用

为了避免循环引用,可以使用 std::weak_ptr。std::weak_ptr 不增加引用计数,它允许 shared_ptr 相互引用,但不会影响对象的生命周期。~

#include <iostream>
#include <memory>

class A; // 前向声明

class B {
public:
    std::weak_ptr<A> ptrA;  // 使用 weak_ptr
    B() { std::cout << "B created\n"; }
    ~B() { std::cout << "B destroyed\n"; }
};

class A {
public:
    std::shared_ptr<B> ptrB;
    A() { std::cout << "A created\n"; }
    ~A() { std::cout << "A destroyed\n"; }
};

int main() {
    std::shared_ptr<A> pA = std::make_shared<A>();
    std::shared_ptr<B> pB = std::make_shared<B>();

    pA->ptrB = pB;
    pB->ptrA = pA;

    // 使用 weak_ptr 解决循环引用
    return 0;
}
----------------输出
A created
B created
A destroyed
B destroyed

http://www.kler.cn/a/442523.html

相关文章:

  • HDFS常用命令
  • IIS服务器部署C# WebApi程序,客户端PUT,DELETE请求无法执行
  • vue3 + ts + element-plus 表格中的input按回车聚焦到下一行
  • 电商大数据的几种获取渠道分享!
  • 数据可视化-4. 漏斗图
  • 国内主流数据库介绍及技术分享
  • vue iframe进行父子页面通信并切换URL
  • 基于Streamlit和OpenAI大模型的Chatbot App支持图片的多模态输入
  • 使用 Copilot 增强创造力:Mighty Media 的卓越数字化之旅
  • 【论文复刻】2021-2012年环境规制影响企业融资约束吗—基于新《环保法》的准自然实验(C刊《证券市场导报》)
  • RPA 在促销活动自动化处理中的创新应用
  • CSS3:重塑网页设计的新力量
  • YOLO目标检测算法
  • 【DevOps工具篇】Gitlab Runner设置(使用Docker in docker作为Runner)
  • LAPACK 程序 SSYEVD 的计算特征值的应用实例 C/Fortran
  • 数据结构 ——哈希表
  • React工具和库面试题目(二)
  • 2024.12.15 TCP/IP 网络模型有哪几层?(二)
  • C++ 的衰退复制(decay-copy)
  • 画一颗随机数