智能指针

xiaoxiao2025-06-02  48

智能指针

智能指针概念RAII思想重重载`*`、`->`运算符重载`->`运算符 为什么需要智能指针智能指针的历史shared_ptr的循环引用如何解决 库中的智能指针使用单个对象多个对象(数组)或特殊处理类型(FILE指针、malloc/free)仿函数 线程安全问题智能指针的简单实现auto_ptrscoped_ptrshared_ptrweak_ptr

智能指针

概念

简单来说智能指针就是一个类似于指针的玩意,帮我们管理一个对象,不需要我们去手动释放指针所指对象。

智能指针(英语:Smart pointer)是一种抽象的数据类型。在程序设计中,它通常是经由类模板来实现,借由模板来达成泛型,借由类别的析构函数来达成自动释放指针所指向的存储器或对象。

智能指针一般会遵循以下两点:

遵循RAII思想——资源分配即初始化重载*、->运算符,有些针对数组的智能指针也会重载[]

RAII思想

RAII是英文Resource Acquisition Is Initialization的缩写,指的是资源分配即初始化。

RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。

重重载*、->运算符

因为对于指针而言,指针访问一个对象内部成员通常是使用解引用*加.或者->来访问,比如指针p指向的一个对象中有一个成员num,那么我们访问使用的是(*p).num或者p->num,但是智能指针本身是个类,所以重载*、->是为了让和指针一样去访问成员,方便使用。

重载->运算符

我们重载运算符的时候,在使用过程中运算符会被消耗掉,比如重载*,*p就相当于operator*(),在智能指针中返回的是该智能指针所指对象。 类比,如果我们用->,实际是相当于operator->(),此时这个运算符就被消耗了,返回一个智能指针所指对象的指针,问题是如果我们要接着访问对象内成员的话,应该还需要一个->,也就是说我们在用的时候应这么用p->->member,但这样极其不便于书写和阅读习惯,因此在这里编译器对其处理成只需要一个->就能访问成员,这里是比较特殊的地方。

为什么需要智能指针

在C++中由于有异常这种扰乱程序执行流程的东西,尽管我们写了new之后马上跟了delete,但是函数里面new了之后还没执行到delete就抛异常跳出函数,导致对象没有被释放造成内存泄漏问题,防不胜防,或者我压根就忘了释放,这个时候就是智能指针登场的时候了。

智能指针本身是一个类,由于类的对象创建时系统会自动调用构造函数,而当对象生命周期结束时,系统会自动调用析构函数,让对象能够自动释放,这样就无需我们手动去管理,也不会出现因执行流被打断造成的内存泄漏问题。

智能指针的历史

auto_ptr:C++98标准中提供了该类的智能指针,但是这个智能指针是个残疾,当使用另一个auto_ptr拷贝时,原指针会被置空,严重影响体验,公司不让用。scoped_ptr:这个智能指针由Boost的大佬们实现,原因是auto_ptr就是个残废,于是有了这个指针,scoped_ptr是auto_ptr的防拷贝版本,解决了auto的不足,日常使用满足需求,公司推荐使用。shared_ptr:该指针也是Boost库中的指针,这个版本引入了引用计数实现共享指针,但是会有智能指针相互循环引用的问题,在特定场景会内存泄漏,公司推荐使用。weak_ptr:Boost库的智能指针,用于解决shared_ptr循环引用的问题,可以接收一个shared_ptr的对象,并指向shared_ptr指向的对象,引用计数独立。unique_ptr/shared_ptr/weak_ptr:C++11标准提供,由于Boost库已经实现的很成熟,因此C++11实现的和其很类似,使用也基本没有差别,其中unique_ptr就是Boost库中的scoped_ptr。

shared_ptr的循环引用

当有如下类

struct ListNode { shared_ptr<ListNode> _next; shared_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } };

如果存在两个指针p1和p2

p1->_next = p2;p2->_prev = p1; 此时存在循环引用问题,析构p1的前提是p2->_prev要析构(因为引用计数为2,光析构一个不行),析构p2的前提是p1->_next要析构,这就很麻烦,到了最后析构的时候,引用计数只减了1,两个对象还是没析构掉,反而出现了内存泄漏。。

如何解决

Boost库提供了一种智能指针叫weak_ptr专门解决这类问题,只需要将上述的结构体定义如下,便能解决,由于weak_ptr不影响shared_ptr的引用计数,所以释放时会正常释放

struct ListNode { weak_ptr<ListNode> _next; weak_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } };

库中的智能指针使用

单个对象

单个对象比较简单,不多赘述,看代码即可

struct TestClass { public: TestClass() { cout << "TestClass()" << endl; } ~TestClass() { cout << "~TestClass()" << endl; } private: int num; }; void test() { unique_ptr<TestClass> up(new TestClass); shared_ptr<TestClass> sp1(new TestClass); shared_ptr<TestClass> sp2(sp1); cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; cout << sp1.get() << endl; cout << sp2.get() << endl; }

多个对象(数组)或特殊处理类型(FILE指针、malloc/free)

多个对象如果默认不处理,最后只会析构一个对象,甚至可能出错,特殊类型也不能用delete简单处理,比如FILE指针必须用fclose处理,这里就涉及到仿函数处理特殊类型了,自定义一个类,重载(),在创建智能指针时把其对象作为第二个参数传入,处理即按照仿函数处理析构,第二个参数就是给我们自定析构用的,默认用delete,自定可以自定析构方式,如delete[]、free、fclose等,实现自动管理。

template <class T> class DeleteArray { void operator()(T* ptr) { cout << "DeleteArray delete array" << endl; delete[] ptr; } }; template <class T> class DeleteFILE { void operator()(T* ptr) { cout << "DeleteArray delete array" << endl; fclose(ptr); } }; void testArray() { unique_ptr<TestClass[]> up_group(new TestClass[10]); //shared智能指针管理数组,自定义删除模式 shared_ptr<TestClass> sp_group(new TestClass[10], DeleteArray<TestClass>()); //管理特殊类型 shared_ptr<FILE> file(fopen("text.txt", "w"), DeleteFILE<FILE>()); }

仿函数

仿函数就是重载了(),比如类A重载了一个函数,有两个int参数,如void operator()(int x1, int x2);,这个时候我们就可以这么调用这个成员,A(1, 2);,由于其调用方式很像函数的调用,但本质是重载了运算符(),因此称之为仿函数。

线程安全问题

使用shared_ptr时会有一个安全问题,由于shared_ptr是可以拷贝的,指向内容可以被多个智能指针访问,如果这些指针在不同的线程内,那么会出现线程安全问题,引用计数也是如此,可能存在短时间内被两个指针访问修改的情况,那么就会出问题。

shared_ptr的线程安全可以分为两层:

引用计数线程安全由指针维护,无需我们多管指针所指向内容需要用户自己去维护,这样就需要我们对临界资源进行控制,linux下使用信号量或者互斥锁,Windows的话。。不清楚

智能指针的简单实现

auto_ptr

template <class T> class AutoPtr { public: AutoPtr(T* ptr = new T) :_ptr(ptr) {} AutoPtr(AutoPtr<T>& sp) :_ptr(sp._ptr) { sp._ptr = NULL; } ~AutoPtr() { delete _ptr; } AutoPtr<T>& operator=(AutoPtr<T>& sp) { //在auto_ptr中,如果赋值就把原指针直接给置空了,这是一件很蠢的事情 _ptr = sp._ptr; sp._ptr = NULL; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; }; //这个版本没有测试用例

scoped_ptr

防拷贝的实现使用delete关键字,删除拷贝构造和operator=(),表明函数已经被删除,无法调用。

template <class T> class ScopedPtr { public: ScopedPtr(T* ptr = new T) :_ptr(ptr) {} ~ScopedPtr() { delete _ptr; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } ScopedPtr(ScopedPtr<T>& sp) = delete; ScopedPtr<T>& operator=(ScopedPtr<T>& sp) = delete; ScopedPtr<T>& operator=(T* ptr) = delete; protected: T* _ptr; }; void testScopePtr() { ScopedPtr<string> sp; //ScopedPtr<string> sp1(sp);//错误,拷贝构造为私有无法调用 //sp1 = sp;//错误,=运算符重载后为私有,无法调用 *sp = "hello world"; cout << *sp << endl; sp->at(0) = 'H';//调用string类的at cout << *sp << endl; }

shared_ptr

template <class T> class SharedPtr { template <class> friend class WeakPtr; public: SharedPtr(T* ptr = NULL) :_ptr(ptr) ,_count(new size_t(1)) {} SharedPtr(SharedPtr<T>& sp) :_ptr(sp._ptr) , _count(sp._count) { (*_count)++; } ~SharedPtr() { if (--(*_count) == 0) { delete _ptr; delete _count; } } SharedPtr<T>& operator=(SharedPtr<T>& sp) { if (_ptr != sp._ptr) { if (--(*_count) == 0) { delete _ptr; delete _count; } _ptr = sp._ptr; ++(*sp._count); _count = sp._count; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; size_t *_count; }; void testSharedPtr() { SharedPtr<string> sp = new string; SharedPtr<string> sp1;//可以使用 sp1 = sp;//可以使用 *sp = "hello world"; cout << *sp << endl; sp->at(0) = 'H';//调用string类的at cout << *sp1 << endl; } struct List { SharedPtr<List> _next; SharedPtr<List> _prev; List() :_next(NULL) , _prev(NULL) {} ~List() { cout << "~List()" << endl; } }; void testSharedPtrCircularReference() { //这里压根就调不到析构函数,因为引用计数互相引用后为2 //出作用域后计数为1,还没达到析构条件 //循环引用问题 SharedPtr<List> p1 = new List(); SharedPtr<List> p2 = new List(); p1->_next = p2; p2->_prev = p1; }

weak_ptr

弱指针用于处理循环引用的问题

template <class T> class WeakPtr { public: WeakPtr() :_ptr(NULL) {} WeakPtr(const SharedPtr<T>& sp) :_ptr(sp._ptr) {} WeakPtr(WeakPtr<T>& wp) :_ptr(wp._ptr) {} WeakPtr<T>& operator=(SharedPtr<T>& sp) { _ptr = sp._ptr; return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } protected: T* _ptr; }; struct ListNode { WeakPtr<ListNode> _next; WeakPtr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } }; // 循环引用 void TestCycle() { SharedPtr<ListNode> n1 = new ListNode; SharedPtr<ListNode> n2 = new ListNode; n1->_next = n2; n2->_prev = n1; }
转载请注明原文地址: https://www.6miu.com/read-5031150.html

最新回复(0)