智能指针
什么是智能指针呢,它是行为类似于指针的类对象,但这种对象还有其他功能。我们为什么要封装智能指针类对象呢?这是因为C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄漏,所以我们会定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
接下来我们模拟实现以下boost库里的一些智能指针了解他们的使用。Auto_ptr
第一种实现方法
#include<iostream>
#include<stdlib.h>
using namespace std;
//模拟实现auto_ptr,非常坑爹什么情况下都不要使用
template <typename T>
class Auto_ptr
{
public:
Auto_ptr(T *ptr = NULL)
:_ptr(ptr)
{}
Auto_ptr(Auto_ptr &a)//拷贝构造函数
{
_ptr = a._ptr;
a._ptr = NULL;//有新的指针使用这块空间为了避免程序崩溃原先的指针
//这快空间脱离关系
}
Auto_ptr & operator=(Auto_ptr& a)
{
if(this != &a)//防止自身赋值
{
if(NULL !=_ptr)
{
delete _ptr;
_ptr = NULL;
}
_ptr = a._ptr;
a._ptr = NULL;
}
return *this;
}
~Auto_ptr()
{
if(NULL != _ptr)
{
delete _ptr;
_ptr = NULL;
cout<<"_ptr has been deleted";
}
}
private:
T *_ptr;
};
第二种实现方法-添加bool变量
template <typename T>
class Auto_ptr
{
public:
Auto_ptr(T *ptr = NULL)
:_ptr(ptr)
,Onlyone(true)
{
if(NULL == ptr)
{
Onlyone = false;
}
}
Auto_ptr(Auto_ptr &a)//拷贝构造函数
{
_ptr = a._ptr;
Onlyone = true;
a.Onlyone = false;//有新的指针使用这块空间为了避免程序崩溃原先管理空间的指针Onlyone置为false
}
Auto_ptr & operator=(Auto_ptr& a)
{
if(this != &a)//防止自身赋值
{
if(false != Onlyone)
{
delete _ptr;
_ptr = NULL;
}
_ptr = a._ptr;
a.Onlyone = false;
Onlyone = true;
}
return *this;
}
~Auto_ptr()
{
if(true == Onlyone)
{
delete _ptr;
_ptr = NULL;
cout<<"_ptr has been deleted";
}
}
private:
T *_ptr;
bool Onlyone;
};
两种方法各有优缺点,这里我们来看一下第二种的Bug.
void FunTest()
{
Auto_ptr<int>ap1(new int)
if(true)
{
Auto_ptr<int>ap2(new int)
}//出了if作用域空间就会被释放,此时使用ap1指针就会出现问题
}
- ScopedPtr
ScopedPtr这个智能指针采取比较暴力的手段,让空间只由自己一个来管理,我们来看看具体的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
using namespace std;
template <typename T>
class ScopedPtr
{
public:
ScopedPtr(T *p = NULL)
:_p(p)
{}
~ScopedPtr()
{
if(NULL != _p)
{
delete _p;
_p = NULL;
}
}
private:
//为了防止浅拷贝的问题出现,使这个类无法被拷贝和赋值
//采用的方法就是将拷贝构造函数和赋值运算符重载函数
//访问权限设为私有,并且只给出声明。
ScopedPtr(const ScopedPtr& s);
T &operator=(const ScopedPtr& s);
private:
T *_p;
};
void FunTest()
{
ScopedPtr<int> sp(new int);
//ScopedPtr<int> sp1(sp);//错误无法完成拷贝
ScopedPtr<int> sp1(new int);
//sp1 = sp;//无法成功赋值
}
int main()
{
FunTest();
system("pause");
return 0;
}
一个类如何防拷贝呢?
1.声明为私有的:这样可以通过友元函数和成员函数拷贝成成功(不可取)
2.声明为公有:可能在类外被定义(不可取)
3.声明为私有(只给出声明)(可以实现)
- SharedPtr
最后我们来模拟boost库中shared_ptr,但这个函数线程不是很安全,一般建议使用scoped_ptr
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
using namespace std;
//共享的智能指针
//使用引用计数
template <typename T>
struct Delete
{
void operator()(T* ptr)
{
if(NULL != ptr)
{
delete ptr;
ptr = NULL;
}
}
};
struct Fclose
{
void operator()(FILE* ptr)
{
if(NULL != ptr)
{
fclose(ptr);
}
}
};
template <typename T,typename _Del = Delete<T>>
class SharedPtr
{
public:
SharedPtr(T *p = NULL,_Del del = Delete<T>())//构造函数
:_p(p)
,_pCount(NULL)//不可以在此处初始化为1,如果p为NULL
,_del(del)
{
if(NULL != _p)
{
_pCount = new int(1);
}
}
SharedPtr(const SharedPtr& sp)//拷贝构造函数
:_p(sp._p)
,_pCount(sp._pCount)
{
if(NULL != _pCount)//注意_pCount为NULL的情况,此时不能解引用,不用++
{
++(*_pCount);
}
}
SharedPtr& operator=(const SharedPtr& sp)
{
if(_p != sp._p)//注意自身赋值,有时候两个不同的对象但里面的的指针指向
//相同不能用(this != &sp)判断出来
{
if(NULL != _pCount)//被复制的对象本身为空
{
if(0 == --(*_pCount))//被赋值的对象自己管理一段空间,需要释放
{
Release();
}
}
_p = sp._p;//和其他对象共同管理
_pCount = sp._pCount;
if(NULL != sp._pCount)//注意判断赋值对象是否为空
{
++(*_pCount);
}
}
return *this;
}
~SharedPtr()
{
Release();
}
private:
void Release()
{
if(0 == --(*_pCount))
{
_del(_p);
delete _pCount;//记得要释放引用计数的空间
_pCount = NULL;
}
}
private:
T *_p;
int *_pCount;
_Del _del;
};
SharedPtr存在线程安全问题,并且也存在循环引用的问题,我们之后会详细讨论,所以建议使用ScopedPtr。