前言
一、智能指针shared_ptr的原理
shared_ptr 是 C++ 标准库提供的一个智能指针类,它的原理基于引用计数(reference counting)。
当我们使用 shared_ptr 管理一个对象时,实际上会有一个额外的数据结构来维护引用计数信息。这个计数器会记录有多少个 shared_ptr 实例共享同一个指针对象。
在创建 shared_ptr 对象时,引用计数会初始化为 1。如果后续将同一指针赋值给其他 shared_ptr 对象,或者把一个已存在的 shared_ptr 对象拷贝构造给新的 shared_ptr 对象,那么引用计数就会增加。
当 shared_ptr 对象超出作用域或被显式设为 nullptr,引用计数会减少。一旦引用计数减到 0,表示没有任何 shared_ptr 对象管理这个指针,其内部的资源就可以被安全释放了。这意味着,资源的生命周期完全由 shared_ptr 进行控制,无需手动释放。
使用 shared_ptr 的好处是,可以多个对象共享一个资源,避免了手动管理资源释放的复杂性和容易出错的风险。它提供了自动化的内存管理,使得代码更加可靠和可维护。
需要注意的是,引用计数的维护是在堆上进行的,而资源本身可能是位于堆上的动态分配内存,也可以是其他形式的资源,如文件句柄等。shared_ptr 的析构函数会自动释放资源,确保资源被正确地释放,从而避免了内存泄漏和资源泄漏。
另外,为了解决循环引用的问题,shared_ptr 引入了 weak_ptr。weak_ptr 是对 shared_ptr 的一种观察者,可以监测所观察的对象是否还存在,但它不会增加引用计数。这样,我们在遇到可能出现循环引用的情况时,可以使用 weak_ptr 打破引用计数的循环,避免资源无法释放的问题。
二、实现
智能指针类包括构造函数、拷贝构造函数、赋值操作符和析构函数。它还重载了解引用操作符和箭头操作符,使其更像原生指针。这个实现使用一个整数变量来存储引用计数,并在拷贝构造函数和赋值操作符中更新引用计数。在析构函数中,如果引用计数为零,则释放内存资源。
1、成员变量以及析构函数
有两个私有成员变量 _ptr 和 _count,分别用于保存实际的指针地址和引用计数。
接下来是 release 函数,用于释放资源。如果计数为 0 或者减少后计数为 0,那么我们就可以销毁资源,释放指针 delete _ptr,并释放计数 delete _count。
T* _ptr;
int* _count;
// 释放资源 如果计数为0或者减减后计数为0 就销毁
void release() {
if (_count && --(*_count) == 0) {
delete _ptr;
delete _count;
}
}
// 析构函数
~MySharedPtr() { release(); }
2、有参构造函数
1、构造函数使用了一个可选参数 ptr,默认值为 nullptr。这意味着我们可以选择传入一个指向资源的原始指针,或者使用默认值创建一个空的智能指针对象。
2、将参数 ptr 赋值给私有成员变量 _ptr。这样,当前的智能指针对象将指向这个资源。
3、在接下来的条件判断中,我们判断 _ptr 是否为空,即是否有资源需要管理。如果 _ptr 不为空,则说明需要创建一个引用计数。
4、在创建引用计数时,我们使用 new 运算符动态分配内存,将引用计数的初始值设置为 1。这代表着当前智能指针对象是唯一管理这个资源的对象。
5、如果 _ptr 为空,则将引用计数的初始值设置为 0,表示当前智能指针对象不持有任何资源。
explicit MySharedPtr(T* ptr = nullptr) : _ptr(ptr) {
if (_ptr) {
_count = new int(1);
} else {
_count = new int(0);
}
}
3、拷贝构造函数
1、通过引用传递拷贝构造函数的参数 other,这表示我们要从另一个智能指针对象进行拷贝构造。
2、将 other 的指针 _ptr 和引用计数 _count 分别赋值给当前智能指针对象的成员变量 _ptr 和 _count。这样,当前智能指针对象将指向和另一个智能指针对象相同的资源。
3、检查 _count 是否存在(非空)。如果存在,则说明所指向的资源已被其他智能指针对象共享。在这种情况下,我们需要增加引用计数 _count 的值,以反映当前智能指针对象的共享情况。
MySharedPtr(const MySharedPtr<T>& other)
: _ptr(other._ptr), _count(other._count) {
if (_count) {
++(*_count);
}
}
4、赋值运算符重载函数
1、首先,我们通过比较 this 指针和 other 的地址,判断是否为自赋值。如果两个指针的地址相同,表示正在进行自我赋值,这种情况下不需要执行赋值操作。
2、在进行赋值操作之前,首先调用 release() 函数对当前智能指针对象的资源进行释放。这个函数会减少当前智能指针对象引用计数,并在引用计数为 0 时释放资源。
3、将 other 的指针 _ptr 和引用计数 _count 分别赋值给当前智能指针对象的成员变量 _ptr 和 _count。这样,当前智能指针对象将指向和另一个智能指针对象相同的资源。
4、检查 _count 是否存在(非空)。如果存在,则说明所指向的资源已被其他智能指针对象共享。在这种情况下,我们需要增加引用计数 _count 的值,以反映当前智能指针对象的共享情况
MySharedPtr<T>& operator=(const MySharedPtr<T>& other) {
if (this != &other) {
// 先对当前的智能指针进行释放
release();
_ptr = other._ptr;
_count = other._count;
if (_count) {
++(*_count);
}
}
return *this;
}
5、重载解引用操作符 *、箭头操作符 ->
1、重载解引用操作符 *:通过重载 * 操作符,我们可以像使用原始指针一样通过智能指针来访问所管理的资源。在这个代码中,解引用操作符直接返回 _ptr 指向的资源,即 *_ptr。
2、重载箭头操作符 ->:通过重载箭头操作符,我们可以让智能指针对象像指针一样使用成员函数和成员变量。在这个代码中,箭头操作符返回 _ptr,这使得我们可以通过智能指针对象使用其管理的资源的成员函数和成员变量。
3、获取引用计数的函数 use_count:这个函数用于获取智能指针对象当前所管理资源的引用计数。如果 _count 存在(非空),则返回 _count 指针指向的值;否则返回 0。通过这个函数,我们可以知道资源同时有多少个智能指针对象共享
~MySharedPtr() { release(); }
// 重载解引用操作符
T& operator*() const { return *_ptr; }
// 重载箭头操作符
T* operator->() const { return _ptr; }
// 获取引用计数
int use_count() const { return _count ? *_count : 0; }
完整代码
#include <iostream>
using namespace std;
template <typename T>
class MySharedPtr {
private:
/* data */
// 两个私有成员 一个记录真实的地址一个记录计数
T* _ptr;
int* _count;
// 释放资源 如果计数为0或者减减后计数为0 就销毁
void release() {
if (_count && --(*_count) == 0) {
delete _ptr;
delete _count;
}
}
public:
explicit MySharedPtr(T* ptr = nullptr) : _ptr(ptr) {
if (_ptr) {
_count = new int(1);
} else {
_count = new int(0);
}
}
// 拷贝构造
// 将 other 的指针 _ptr 和引用计数 _count
// 分别赋值给当前智能指针对象的成员变量 _ptr 和
// _count。这样,当前智能指针对象将指向和另一个智能指针对象相同的资源。 检查
// _count
// 是否存在(非空)。如果存在,则说明所指向的资源已被其他智能指针对象共享。在这种情况下,我们需要增加引用计数
// _count 的值,以反映当前智能指针对象的共享情况。
// 拷贝构造函数
MySharedPtr(const MySharedPtr<T>& other)
: _ptr(other._ptr), _count(other._count) {
if (_count) {
++(*_count);
}
}
// 赋值操作
MySharedPtr<T>& operator=(const MySharedPtr<T>& other) {
if (this != &other) {
// 先对当前的智能指针进行释放
release();
_ptr = other._ptr;
_count = other._count;
if (_count) {
++(*_count);
}
}
return *this;
}
// 析构函数
~MySharedPtr() { release(); }
// 重载解引用操作符
T& operator*() const { return *_ptr; }
// 重载箭头操作符
T* operator->() const { return _ptr; }
// 获取引用计数
int use_count() const { return _count ? *_count : 0; }
};
int main() {
MySharedPtr<int> sp1(new int(10));
std::cout << "sp1 use_count: " << sp1.use_count() << std::endl;
MySharedPtr<int> sp2(sp1);
std::cout << "sp1 use_count: " << sp1.use_count() << std::endl;
std::cout << "sp2 use_count: " << sp2.use_count() << std::endl;
return 0;
}