引言
在 C++ 动态内存管理中,除了 auto_ptr 和 unique_ptr 之外,还有一种智能指针 shared_ptr,它可以让多个指针共享同一个动态资源,并且能够自动释放资源。shared_ptr 通过引用计数的方式来管理内存,能够避免程序中出现悬空指针和内存泄漏等问题。本文将介绍 shared_ptr 的简介和使用方法,并提供一个 C++ 模拟实现,以帮助读者更好地理解其原理和实现。
一、简介
std::shared_ptr
是 C++11 标准库中的一个智能指针,它可以让多个指针共享同一个动态资源,并且能够自动释放资源。shared_ptr
通过引用计数的方式来管理内存,能够避免程序中出现悬空指针和内存泄漏等问题。
与 std::auto_ptr 和 std::unique_ptr 不同,std::shared_ptr 可以被多个指针所共享。当一个 shared_ptr 被赋值给另一个 shared_ptr 或者被拷贝构造时,它所管理的资源的引用计数会增加。只有在最后一个 shared_ptr 被销毁时,才会释放所管理的资源。这种语义被称为“共享所有权”。
🔴std::shared_ptr官方文档
二、成员函数
✅为了方便用户使用 shared_ptr
智能指针,shared_ptr<T>
模板类还提供有一些实用的成员方法,它们各自的功能如下表所示
成员方法名 | 功能 |
operator=() | 重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。 |
operator * () | 重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。 |
operator->() | 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 |
swap() | 交换 2 个相同类型 shared_ptr 智能指针的内容。 |
reset() | 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。 |
get() | 获得 shared_ptr 对象内部包含的普通指针。 |
use_count() | 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。 |
unique() | 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。 |
operator bool() | 判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false ;反之,返回 true 。 |
⭕当然除此之外,C++11 标准还支持同一类型的 shared_ptr
对象,或者 shared_ptr
和 nullptr
之间,进行 ==
,!=
,<
,<=
,>
,>=
运算。
三、使用示例
下面是一个使用 std::shared_ptr
的示例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> sp1(new int(42)); // 创建一个指向整数 42 的 shared_ptr std::shared_ptr<int> sp2 = sp1; // sp2 和 sp1 现在都指向同一个对象 std::cout << *sp1 << " " << *sp2 << std::endl; // 输出结果为 42 42 *sp1 = 10; std::cout << *sp1 << " " << *sp2 << std::endl; // 输出结果为 10 10 sp1.reset(); // 释放 sp1 的所有权 std::cout << *sp2 << std::endl; // 输出结果为 10 sp2.reset(); // 释放 sp2 的所有权 return 0; }
在这个示例中,我们首先创建了一个指向整数 42 的 shared_ptr,然后将其赋值给另一个 shared_ptr。由于共享所有权的语义,它们都指向同一个对象。接着,我们修改了 sp1 指向的对象的值,然后释放了 sp1 的所有权,此时 sp2 仍然可以访问该对象。最后,我们释放了 sp2 的所有权,整个示例结束。
四、C++模拟实现
#include <iostream> #include <mutex> using namespace std; template<class T> class shared_ptr { public: // 构造函数 explicit shared_ptr(T* ptr = nullptr) : _ptr(ptr) , _pcount(new int(1)) , _pmtx(new mutex) {} // 析构函数 ~shared_ptr() { Release(); } /* 释放资源 Release() 方法减少引用计数,并根据引用计数的值来判断是否需要删除指向的堆内存对象和引用计数对象。 在操作之前,我们使用互斥量 _pmtx 进行加锁,以保证线程安全。*/ void Release() { _pmtx->lock(); bool deleteFlag = false; if (--(*_pcount) == 0) { if (_ptr) { // 删除器进行删除 _del(_ptr); } delete _pcount; deleteFlag = true; } _pmtx->unlock(); if (deleteFlag) { delete _pmtx; } } // 增加引用计数 void AddCount() { _pmtx->lock(); ++(*_pcount); _pmtx->unlock(); } // 拷贝构造函数 shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr) , _pcount(sp._pcount) , _pmtx(sp._pmtx) { AddCount(); } // 赋值运算符重载 shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if (_ptr != sp._ptr) { Release(); _ptr = sp._ptr; _pcount = sp._pcount; _pmtx = sp._pmtx; AddCount(); } return *this; } // operator*() 重载 T& operator*() { return *_ptr; } // operator->() 重载 T* operator->() { return _ptr; } // get() 方法 T* get() { return _ptr; } // use_count() 方法 int use_count() { return *_pcount; } // swap() 方法,交换 2 个 shared_ptr 智能指针的内容 void swap(shared_ptr<T>& sp) noexcept { std::swap(_ptr, sp._ptr); std::swap(_pcount, sp._pcount); std::swap(_pmtx, sp._pmtx); } // reset() 方法,重置 shared_ptr 智能指针对象 void reset(T* ptr = nullptr) { // 释放原有资源 Release(); // 重新赋值 _ptr = ptr; _pcount = new int(1); _pmtx = new mutex; } private: T* _ptr; // 指向堆内存对象的指针 int* _pcount; // 引用计数的指针 mutex* _pmtx; // 保护引用计数的互斥量 // 包装器 function<void(T*)> _del = [](T* ptr) { cout << "lambda delete:" << ptr << endl; delete ptr; }; };
以上代码是一个简化版的 shared_ptr 智能指针模板类的实现。智能指针是 C++ 中的一个重要工具,可以帮助开发者更方便地管理动态内存。在手动管理内存时,很容易出现内存泄漏和悬垂指针等问题,而使用智能指针则可以自动管理对象的生命周期,避免这些问题。
该 shared_ptr 类实现了一个引用计数机制,它通过维护一个引用计数,来判断指向的堆内存对象是否应该被释放。当有多个 shared_ptr 指向同一个堆内存对象时,引用计数会增加;当 shared_ptr 对象销毁时,引用计数会减少。当引用计数为 0 时,就可以释放堆内存对象了。
🚨🚨注意:该 shared_ptr 类是一个简化版实现,可能存在一些问题,不适用于生产环境中。在实际开发中,我们可以使用标准库中的 shared_ptr 类或其他第三方库中的智能指针实现。
五、std::shared_ptr的线程安全问题
标准库中的 std::shared_ptr
是线程安全的,可以在多线程环境下使用。它通过使用原子操作和引用计数来实现线程安全。
在 std::shared_ptr 内部,引用计数是一个原子操作,确保多个线程可以安全地对其进行增加和减少操作。当有一个新的 std::shared_ptr 指向同一块堆内存时,引用计数会增加;当某个 std::shared_ptr 对象销毁时,引用计数会减少。只有当引用计数为 0 时,才会释放堆内存。
此外,std::shared_ptr 还使用了原子操作来保证多个线程之间对智能指针对象的访问是互斥的。这意味着,在多线程环境下,不同的线程可以同时拷贝和赋值 std::shared_ptr 对象,而不会出现数据竞争的问题。
🚨🚨注意:虽然 std::shared_ptr 提供了线程安全的引用计数和访问控制,但它本身并不保证所指向的对象是线程安全的。如果多个线程同时访问和修改同一块内存,则需要额外的同步机制来确保线程安全性。
六、总结
shared_ptr 是C++中的智能指针类,通过引用计数机制管理堆内存对象的生命周期,并使用原子操作确保引用计数的线程安全性。它支持拷贝构造和赋值运算符重载,可以安全地共享指向同一块堆内存的对象。此外,shared_ptr提供了方便的访问和操作接口,是一种方便而安全的资源管理工具。
当然,从上述方面来看,shared_ptr 确实是一种非常强大的工具。然而,它也存在一个缺点,即“循环引用问题”。在下一篇文章中,我将介绍与之相关的知识点——weak_ptr。weak_ptr 是一种特殊的智能指针,用于解决 shared_ptr 循环引用可能导致的内存泄漏问题。通过引入weak_ptr,我们可以打破循环引用,避免内存泄漏,并在需要时安全地访问对象。敬请期待下一篇文章的发布!
温馨提示
感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!