智能指针(带引用计数、不带引用计数、自定义删除器)

2024-05-01 16:28

本文主要是介绍智能指针(带引用计数、不带引用计数、自定义删除器),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.不带引用计数的智能指针

1.1 auto_ptr

1.2 scoped_ptr

 1.3 unique_ptr

2.带引用计数的智能指针

2.1 强智能指针循环引用(交叉引用)问题

2.2 多线程访问共享对象问题

2.3 自定义智能指针(shared_ptr仿写)

 3.自定义删除器


1.不带引用计数的智能指针

1.1 auto_ptr

auto_ptr是C++函数库中的

ptr1指向内存区域。

拷贝构造后,ptr2指向内存区域,ptr1置空。

auto_ptr解决浅拷贝的策略:

永远让最后一个指针管理资源,之前的指针都置成nullptr ,如果再访问之前的指针,程序会崩溃。所以,不推荐使用。

#include <iostream>
#include <memory>
using namespace std;int main()
{	auto_ptr<int> ptr1(new int);auto_ptr<int> ptr2(ptr1);*ptr2 = 20;cout << *ptr1 << endl;
}

1.2 scoped_ptr

解决浅拷贝的方法简单粗暴,直接删除拷贝构造和赋值运算符重载函数,不支持赋值功能,赋值的话会报错。

scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

从 C++11 开始,std::unique_ptr 成为了标准库中的一部分,它提供了与 scoped_ptr 类似的功能,但更加完善和灵活。std::unique_ptr 支持自定义删除器(deleter),可以与标准库容器(如 std::vector)一起使用,并且提供了移动语义,允许在不进行深复制的情况下转移所有权。 

 1.3 unique_ptr

同scoped_ptr一样,也删除了拷贝构造函数和赋值运算符重载函数,但是支持右值引用作为参数的函数。

unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;unique_ptr(unique_ptr<T>&& src);
unique_ptr<T>& operator=(unique_ptr<T>&& src);
template <typename T>
unique_ptr<T> getSmartPtr()
{unique_ptr<T> ptr(new T());return ptr;
}
int main()
{unique_ptr<int> p1(new int);//unique_ptr<int> p2(p1);//错误,拷贝构造函数已在底部被删除//std::move=>C++11右值引用  std::move得到当前变量的右值类型unique_ptr<int> p2(std::move(p1));unique_ptr<int> ptr1 = getSmartPtr<int>();ptr1 = getSmartPtr<int>();
}

unique_ptr同样是让最后一个指针管理资源,之前的指针都置成nullptr,与auto_ptr不同的是,unique_ptr的这个特点是用户可以感知到的,因为用户如果需要赋值,必须要使用move,将原指针管理的资源挪到现指针。

2.带引用计数的智能指针

主要介绍shared_ptr和weak_ptr两个智能指针

什么是带引用计数的智能指针?

当允许多个智能指针指向同一个资源的时候,每一个智能指针都会给资源的引用计数加1,当一个智能指针析构时,同样会使资源的引用计数减1,这样最后一个智能指针把资源的引用计数从1减到0时,就说明该资源可以释放了,由最后一个智能指针的析构函数来处理资源的释放问题,这就是引用计数的概念。

要对资源的引用个数进行计数,那么大家知道,对于整数的++或者- -操作,它并不是线程安全的操作,因此shared_ptr和weak_ptr底层的引用计数已经通过CAS操作,保证了引用计数加减的原子特性,因此shared_ptr和weak_ptr本身就是线程安全的带引用计数的智能指针。

曾经有一道面试的问题这样问“shared_ptr智能指针的引用计数在哪里存放?”,当然,这个问题需要看shared_ptr的源码了,如下:

private:/*下面这两个是shared_ptr的成员变量,_Ptr是指向内存资源的指针,_Rep是指向new出来的计数器对象的指针,该计数器对象包含了资源的一个引用计数器count*/element_type * _Ptr{nullptr};_Ref_count_base * _Rep{nullptr};

因此,shared_ptr智能指针的资源引用计数器在内存的heap堆上。shared_ptr一般被称作强智能指针,weak_ptr被称作弱智能指针。

2.1 强智能指针循环引用(交叉引用)问题

如下代码:

#include <iostream>
#include <memory>
using namespace std;class B; // 前置声明类B
class A
{
public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1ptra->_ptrb = ptrb;// A对象的成员变量_ptrb也指向B对象,B的引用计数为2ptrb->_ptra = ptra;// B对象的成员变量_ptra也指向A对象,A的引用计数为2cout << ptra.use_count() << endl; // 打印A的引用计数结果:2cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2/*出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放,导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题”*/return 0;
}

测试结果:

 

可以看到,A和B对象并没有进行析构,造成内存泄露,原因如下:

能够看出“交叉引用”的问题所在,就是对象无法析构,资源无法释放,那怎么解决这个问题呢?请注意强弱智能指针的一个重要应用规则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr。

代码如下:

_ptra.lock()方法将弱指针提升为强指针,弱指针只能观察资源而不能使用资源,提升为强智能指针后就可以访问类A中的成员方法了。


class B;
class A
{
public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }void testA() { cout << "非常好用的方法" << endl; }weak_ptr<B> _ptrb;
};class B
{
public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }void func(){shared_ptr<A> ps = _ptra.lock();//提升方法,转变为强指针if (ps != nullptr)//提升成功,(指针还在,没有被释放){ps->testA();}}weak_ptr<A> _ptra;//无法访问资源
};int main()
{shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->_ptrb = pb;pb->_ptra = pa;cout << pa.use_count() << endl;cout << pb.use_count() << endl;return 0;
}

测试结果:

 

2.2 多线程访问共享对象问题

智能指针解决多线程访问共享对象的线程安全问题

2.3 自定义智能指针(shared_ptr仿写)

//对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:RefCnt(T* ptr=nullptr):mptr(ptr){if (mptr != nullptr)mcount = 1;}void addRef() { mcount++; }//增加资源的引用计数int delRef() { return --mcount; }
private:T* mptr;int mcount;//目前实现的,mcount++,--mcount不是线程安全的,库中采用的是原子类型,atomic_int  CAS
};
template<typename T>
class CSmartPtr//shared_ptr
{
public:CSmartPtr(T* ptr = nullptr) :mptr(ptr) {mpRefCnt = new RefCnt<T>(mptr);}~CSmartPtr() {if (0 == mpRefCnt->delRef()){delete mptr;mptr = nullptr;}}T& operator*() { return *mptr; }T* operator->() { return mptr; }CSmartPtr(const CSmartPtr<T>& src):mptr(src.mptr),mpRefCnt(src.mpRefCnt){mpRefCnt->addRef();}CSmartPtr<T>& operator=(const CSmartPtr<T>& src){if (this == &src)return *this;if (0 == mpRefCnt->delRef()){delete mptr;}mptr = src.mptr;mpRefCnt = src.mpRefCnt;mpRefCnt->addRef();return *this;}
private:T* mptr;//指向资源的指针RefCnt<T> *mpRefCnt;//指向该资源引用计数对象的指针
};int main()
{CSmartPtr<int> ptr1(new int);CSmartPtr<int> ptr2(ptr1);CSmartPtr<int> ptr3;ptr3 = ptr2;*ptr1 = 20;cout << *ptr2 << " " << *ptr3 << endl;return 0;
}

 3.自定义删除器

我们经常用智能指针管理的资源是堆内存,当智能指针出作用域的时候,在其析构函数中会delete释放堆内存资源,但是除了堆内存资源,智能指针还可以管理其它资源,比如打开的文件,此时对于文件指针的关闭,就不能用delete了,这时我们需要自定义智能指针释放资源的方式,先看看unique_ptr智能指针的析构函数代码,如下:

~unique_ptr() noexcept
{	// destroy the object
if (get() != pointer()){this->get_deleter()(get()); // 这里获取底层的删除器,进行函数对象的调用}
}template<typename T>class default_delete{public:void operator()(T* ptr){delete ptr;}
};

从unique_ptr的析构函数可以看到,如果要实现一个自定义的删除器,实际上就是定义一个函数对象而已,代码如下:

#include <functional>template<typename T>
class MyDeletor
{
public:void operator()(T* ptr)const{cout << "call MyDeletor.operator()" << endl;delete[]ptr;}
};template<typename T>
class MyFileDeletor
{
public:void operator()(T* ptr)const{cout << "call MyDeletor.operator()" << endl;fclose(ptr);}
};int main()
{unique_ptr<int, MyDeletor<int>> ptr1(new int[100]);//delete []ptr1unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt", "w"));return 0;
}

当然这种方式需要定义额外的函数对象类型,不推荐,可以用C++11提供的函数对象function和lambda表达式更好的处理自定义删除器,代码如下:

int main()
{//lambda表达式=》函数对象 functionunique_ptr<int, function<void(int*)>> ptr1(new int[100],[](int* p)->void {cout << "call lambda release new int[100]" << endl;delete[] p;});unique_ptr<FILE, function<void(FILE*)>> ptr2(fopen("data.txt", "w"),[](FILE* p)->void {//指向返回值cout << "call lambda release new fopen" << endl;fclose(p);});return 0;
}

这篇关于智能指针(带引用计数、不带引用计数、自定义删除器)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/952136

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

智能交通(二)——Spinger特刊推荐

特刊征稿 01  期刊名称: Autonomous Intelligent Systems  特刊名称: Understanding the Policy Shift  with the Digital Twins in Smart  Transportation and Mobility 截止时间: 开放提交:2024年1月20日 提交截止日

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积水检测系统,结合深度学习和直观的图形界面,为用户提供高效的解决方案。 源码地址: PyQt5+YoloV5 实现积水检测系统 预览: 项目背景

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非