您的位置:首页 > 编程语言 > C语言/C++

C++基础之智能指针

2017-05-20 16:58 176 查看

一.智能指针的引入:

1. 当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。

2. 智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。

二.智能指针的原理

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。

三.智能指针的演变过程:

auto_ptr         管理权转让(不建议使用)
scoped_ptr       防拷贝
shared_ptr       引用计数


一. 模拟实现auto_ptr

1. 实现原理

资源的转移,AutoPtr的成员变量主要有T* _ptr, bool _owner,主要实现原理是在构造对象时赋予其空间的所有权,在析构函数中通过_owner的真假来释放所有权,并且在拷贝或赋值后将_owner设为false,转移空间的所有权。但此做法的问题是:如果要释放一个拷贝出来的对象,虽然原对象的_owner为false,但会多次释放同一块空间,导致内存泄漏。s

2. 代码

template<class T>
class AutoPtr
{
public:
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* GetPtr()//返回原指针_ptr
{
return  _ptr;
}

T* operator->()
{
return _ptr;
}

~AutoPtr()
{
cout << "~AutoPtr()" << endl;
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}

private:
T* _ptr;
};


3. 出现的问题


* 以下代码会导致ap1访问不了原来的空间

AutoPtr<int> a(p);
AutoPtr<int> ap1(p);
AutoPtr<int> ap2(ap1);
*ap1 = 10;
*ap2 = 20;


*不能用普通类型的引用去引用一个const类型的常量

const AutoPtr<int> ap1(p);
AutoPtr<int> ap2(ap1);//错误做法,因为ap1被改变


*

void FunTest(AutoPtr<int> ap)
{}
int main()
{
AutoPtr<int> ap1(new int);
FunTest(ap1);//因为传值时会调用拷贝构造函数,故ap1被赋为NULL;
return 0;
}


二. 模拟实现scoped_ptr

实现原理

因为智能指针容易出现拷贝时释放两次的情况,所以ScopedPtr主要是为了防止拷贝,防止拷贝的两条必要条件是:①设置保护限定符(将拷贝构造函数和赋值运算符的重载设置成保护或者私有的,这样就可以保证其他人在不知情的情况下定义拷贝构造和赋值运算符重载了);②对拷贝构造函数和赋值运算符重载只声明不定义(如果不这么做,编译器就会自动合成拷贝构造和赋值运算符重载,就实现不了防拷贝的目的了)。

代码

template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}

T& operator*()
{
return *_ptr;
}

T* GetPtr()//返回原指针_ptr
{
return  _ptr;
}

T* operator->()
{
return _ptr;
}

~ScopedPtr()
{
cout << "~ScopedPtr()" << endl;
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}

protected://防拷贝:此处为privated或protecte;拷贝构造和赋值函数只声明不定义
ScopedPtr(const ScopedPtr<T> &sp);
ScopedPtr<T>& operator==(const ScopedPtr<T> &sp);

private:
T* _ptr;
};


3. 出现的问题


*在调用完成后ap1的_owner会被置为false,而不能进行赋值运算

int FunTest(AutoPtr<int> ap)
{}
int main()
{
FunTest();
AutoPtr<int> ap1(new int);
FunTest(ap1);//
*ap1 = 10;
return 0;
}


*执行完成后调用了析构函数,ap1会成为野指针

int FunTest()
{
AutoPtr<int> ap1(new int);
if(true)
{
AutoPtr<int> ap2(ap1);
}
}


三. 模拟实现shared_ptr

1. 实现原理

SharedPtr顾名思义就是共享指针,思想是引用计数。引入指针变量pCount,指向同一块空间对其计数,当只有一个指针指向空间时再释放空间,这样可以解决多个指针指向同一块空间释放多次导致内存泄漏的问题。

2. 代码

template<class T,class Deleter=Del<T>>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
, _pCount(NULL)
{}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
if(_ptr)
++*_pCount;
}
SharedPtr<T>& operator=(SharedPtr<T> sp)
{
if(this!= &sp)
{
if(_ptr && 0==(--*_pCount))
{
delete _ptr;
delete _pCount;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
~SharedPtr()
{
cout << "~SharedPtr()" << endl;
if (--(*_pCount) == 0  && _ptr)
{
delete(_ptr);
delete _pCount;
}
}
T& operator*()  //访问的是原生态指针
{
return *_ptr;
}
T* GetPtr()//返回原指针_ptr
{
return  _ptr;
}
T* operator->()  //访问的是对象所管理的空间
{
return _ptr;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息