c++智能指针那些事

来源:互联网 发布:网络管理和信息安全 编辑:程序博客网 时间:2024/06/05 03:30

在总结智能指针前,要先跟大家说一说RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 

他的思想是资源分配即初始化,定义一个类来封装资源的的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确释放和初始化。
      
RAII要求,资源的有效期与持有资源的对象的生命周期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。


智能指针

智能指针就是基于RAII的要求去使用模板类,自动化的去管理动态资源释放(不管理资源的创建),智能指针看上去是指针,实际上是赋予了定义的对象,智能指针(Smart pointer)实际上是存储指向动态开辟(堆)对象指针的类,用于生存周期的控制,确保能够自动正确的销毁动态分配的对象,防止内存泄漏。


1.Auto_ptr:独占所有权,所有权转移

为实现auto_ptr,我们需要实现该类的构造、拷贝构造、赋值运算符的重载、operator *、operato->等成员函数,重载operator *、operato->是为了让他能够像原生指针那样访问。

那么在auto_ptr类中的拷贝构造是如何实现浅拷贝引起的析构多次的问题呢?

auto_ptr版本中,采用的管理权转移的方法,即由对象a构造出对象b来以后,a对象将被置成NULL,由对象b管理这块空间,a不能访问,这样做的缺陷是不能实现真正的拷贝构造和赋值运算符重载的目的。

auto_ptr源码如下

template <class T>class AutoPtr{public:AutoPtr(){_ptr = NULL;}//构造函数AutoPtr(T* ptr){_ptr = ptr;}//拷贝构造---使用所有权的转移AutoPtr(AutoPtr<T>& ap){_ptr = ap._ptr;ap._ptr = NULL;}//赋值运算符的重载---使用所有权的转移AutoPtr<T>& operator=(AutoPtr<T>& ap){if(this!=&ap){delete _ptr;_ptr = ap._ptr;ap._ptr = NULL;}return *this;}//重载*---可对智能指针解引用T& operator*(){return *_ptr;}//重载->--访问自定义对象T* operator->(){return _ptr;}~AutoPtr(){cout<<"delete"<<endl;delete _ptr;}protected:T* _ptr;};


2.scoped_ptr:独占所有权,防拷贝


scoped_ptr的实现原理是防止对象间的拷贝和赋值,即拷贝构造和赋值运算符的重载放入了保护或私有的访问限定符中,只声明不定义,防止他人在类外拷贝。简单粗暴的解决了auto_ptr的缺陷,提高了安全性,却缺失了某些功能。

scoped_ptr源码如下:

#pragma once#include <iostream>using namespace std;template<class T>class ScopedPtr{public:ScopedPtr(){_ptr = NULL;}//构造函数ScopedPtr(T* ptr){_ptr = ptr;}//重载*---可对智能指针解引用T& operator*(){return *_ptr;}//重载->--访问自定义对象T* operator->(){return _ptr;}~ScopedPtr(){cout<<"delete"<<endl;delete _ptr;}protected://拷贝构造---使用所有权的转移ScopedPtr(const ScopedPtr<T>& ap);//赋值运算符的重载---使用所有权的转移ScopedPtr<T>& operator=(const ScopedPtr<T>& ap);protected:T* _ptr;};

3.shard_ptr:共享所有权,引用计数

shard_ptr的实现原理是通过引用计数实现的,拷贝或赋值时引用计数+1,析构时只当引用计数减到0才释放空间,否则只将引用计数-1即可。


定制删除器:

由于每种指针释放资源的方式不同,比如数组用delete[]删除,文件用fclose关闭等。我们通过不同的资源选择不同释放方式。定制删除器就是通过仿函数来是实现该功能。

带有定制删除其的shard_ptr的实现

#pragma once#include <iostream>using namespace std;template<class T, class Del>class ShardPtr{public:ShardPtr(){_ptr = NULL;_refcount = new int(0);}ShardPtr(T* ptr){_ptr = ptr;_refcount = new int(1);}ShardPtr(const ShardPtr<T,Del>& sp){_ptr = sp._ptr;_refcount = sp._refcount;++(*_refcount);}ShardPtr<T,Del>& operator=(const ShardPtr<T,Del>& sp){if(this!=&sp){Release();_ptr = sp._ptr;_refcount = sp._refcount;++(*_refcount);}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~ShardPtr(){Release();}inline void Release(){if(--(*_refcount) == 0){delete _refcount;_del(_ptr);}}protected:T* _ptr;int* _refcount;//引用计数Del _del;//     定制删除器};//定制删除器 仿函数template<class T>class Delete{public:void operator()(T* ptr){delete ptr;}};template<class T>class DeleteArray{public:void operator()(T* ptr){delete[] ptr;}};template<class T>class Free{public:void operator()(T* ptr){free(ptr);}};template<class T>class Fclose{public:void operator()(T* ptr){fclose(ptr);}};void ShardPtrTest(){int* p = new int;ShardPtr<int,Delete<int>> sp1;ShardPtr<string,DeleteArray<string>>sp2(new string[10]);}
当然,shard_ptr也有一些缺陷,他会引起循环引用和线程安全的问题

对于线程安全的问题我们可以通过给线程间在改变引用计数时加一把互斥锁解决

对于循环引用:循环引用就是指两个对象相互调用,造成相互制约的关系,导致智能指针不能释放,具体看下面例子

struct Node{ShardPtr<Node> _prev;ShardPtr<Node> _next;int _data;};void ShardPtrTest(){ShardPtr<Node> cur(new(Node));ShardPtr<Node> next(new(Node));cur->_next = next;next->_prev = cur;}

这段代码定义了一个双向链表的结点,让后让两个链表结点相连,在进行析构时,cur释放需要先释放next,next释放依赖于cur,相互制约,形成循环引用,为了解决循环引用,我们引入了week_ptr。



4.weak_ptr:弱引用智能指针(与shard_ptr有依附关系)

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它。由于weak_ptr不改变引用计数,所以我们可以用来解决shared_ptr引起的 循环引用问题,如双向链表的释放,我们只需定义链表节点时定义为weak_ptr类型的智能指针即可解决问题。weak_ptr不会使引用计数+1,故不会造成循环引用。最后,作为补充:weak_ptr必须与shared_ptr配合使用,不能单独使用.

struct Node{weak_ptr<Node> _prev;weak_ptr<Node> _next;int _data;};


原创粉丝点击