资源分配即初始化,定义一个类来封装资源的的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确释放和初始化
RAII要求,资源的有效期与持有资源的对象的⽣命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
智能指针
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。智能指针就是使用模板类,按照RAII的要求,自动化地管理动态资源的释放(不管理资源的创建),智能指针看上去是指针,实际上是赋予了定义的对象。
1、auto_ptr:独占所有权,所有权转移
为实现auto_ptr我们需要实现该类的构造、拷贝构造、赋值运算符重载、operator*、operator->等成员函数,重载*和->是为了可以使只能指针像原生指针一样访问。问:在auto_ptr类中如何解决拷贝构造中的浅拷贝引起的析构多次的问题呢?
auto_ptr版本的智能指针采用的是管理权转移的方法,即由a构造出b对象之后,a对象置为NULL,即之后不能再访问a对象,只有b一个对象维护该空间。
缺点:不能真正实现拷贝构造和赋值运算符重载的目的
auto_ptr源码:
#pragma once
using namespace std;
template<class T>
class AutoPtr
{
public:
AutoPtr()
:_ptr(NULL)
{}
AutoPtr(T* ptr) //构造函数
:_ptr(ptr)
{}
//拷贝构造---管理权的转移
AutoPtr(AutoPtr<T>& ap)
{
_ptr = ap._ptr;
ap._ptr = NULL; //此时ap的_ptr已经失效,不可访问
}
//赋值运算符的重载---管理权的转移
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;
};
struct AA //自定义类型同样适用于智能指针
{
int a1=0;
int a2=0;
};
void TestAutoPtr()
{
int* p1 = new int;
AutoPtr<int> ap1;
//AutoPtr<int> ap2(ap1);
//AutoPtr<int> ap3;
//ap3 = ap1;
AutoPtr<AA> ap4 = new AA;
ap4->a1 = 10;
cout << ap4->a1 << endl;
cout << ap4->a2 << endl;
}
2、scoped_ptr:独占所有权,防拷贝
scoped_ptr的实现原理是防止对象间的拷贝和赋值,即将拷贝构造和赋值运算符重载放入保护或私有的访问限定符内,只声明不定义防止他人在类外拷贝。简单粗暴地解决了auto_ptr的缺点,提高了代码的安全性,但是导致功能不完整。
在一般的情况下,如果不需要对于指针的内容进行拷贝,赋值操作,而只是为了防止内存泄漏的发生,scoped_ptr完全可以胜任。
缺点:scoped_ptr的功能不完整
scoped_ptr源码:
#pragma once
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>& sp); //放在保护的访问限定符中,防止被类外修改
ScopedPtr<T> operator=(const ScopedPtr<T>& sp);
protected:
T* _ptr;
};
struct BB
{
int a1 = 0;
int a2 = 0;
};
void TestScopedPtr()
{
int* p = new int;
ScopedPtr<int> sp1;
// ScopedPtr<int> sp2(sp1); //编译不通过
// sp2 = sp1;
BB* p1 = new BB;
ScopedPtr<BB> sp3(p1);
sp3->a1 = 10;
cout << sp3->a1 <<endl;;
}
3、shared_ptr:共享所有权,引用计数
shared_ptr的实现原理是通过引用计数来实现,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可。
定制删除器:
由于每种指针释放资源的方式不同,比如数组用delete[]释放资源,文件用fclose关闭等。我们需要针对不同的资源选择合适的释放方式,定制删除器便是通过仿函数来完成该功能。
带有定制删除器的shared_ptr源码:
#pragma once
using namespace std;
//实现定制删除器
template < class T, class Del >
class SharedPtr
{
public:
SharedPtr()
:_ptr(NULL)
, _refCount(new int(0))
{}
SharedPtr(T* ptr)
:_ptr(ptr)
, _refCount(new int(1))
{}
SharedPtr(const SharedPtr<T, Del>& sp)
:_ptr(sp._ptr)
, _refCount(sp._refCount)
{
(*_refCount)++;
}
SharedPtr<T, Del> operator=(const SharedPtr<T, Del>& sp)
{
if (_ptr != sp._ptr)
{
Release(); //释放被复制成员的原始空间
_ptr = sp._ptr;
_refCount = sp._refCount;
(*_refCount)++;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~SharedPtr()
{
//cout << "delete" << endl;
Release();
}
//将引用计数减一,若没有对象维护该空间则释放
inline void Release()
{
if (--*_refCount == 0)
{
cout << "Release ptr: 0x" << _ptr << endl;
delete _refCount;
//delete _ptr;
_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 TestSharedPtr()
{
int* p = new int;
SharedPtr<int,Delete<int>> sp1;
sp1 = p;
SharedPtr<string, DeleteArray<string>> sp2(new string[10]);
SharedPtr<double, Free<double>> sp3((double*)malloc(sizeof(double)*10));
//SharedPtr<FILE, Fclose<FILE>> sp4(fopen("text.txt", "r"));
}
缺点:看似很完美的东西,往往都隐藏着缺陷,比如shared_ptr就会引起循环引用和线程安全的问题。一、线程安全问题我们可以通过给线程间在改变引用计数时加一把互斥锁解决
二、循环引用:就是指两个对象进行相互引用,造成相互制约的关系,导致智能指针不能释放的问题,具体我们举例说明:
struct Node
{
shared_ptr<Node> _prev;
shared_ptr<Node> _next;
int _data;
};
void TestSharedPtr()
{
shared_ptr<Node> cur(new(Node));
shared_ptr<Node> next(new(Node));
cur->_next = next;
next->_prev = cur;
}
这段代码定义了一个双向链表的节点,然后让两个链表节点相连。在进行析构时,cur要释放需要先释放next,next释放依赖于cur,相互制约,形成循环引用。为了解决循环引用的问题,我们需要借助weak_ptr。
4、weak_ptr:弱引用智能指针(与shared_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,故不会造成循环引用。
struct Node
{
weak_ptr<Node> _prev;
weak_ptr<Node> _next;
int _data;
};
智能指针总结:
auto_ptr: 独占所有权---管理权转移
scoped_ptr: 独占所有权---防拷贝(简单粗暴)
shared_ptr: 共享所有权---增加引用计数
weak_ptr: 弱引用指针---shared_ptr的附属
智能指针的发展:
C++98/03: auto_ptr
boost第三方库: scoped_ptr、shared_ptr、weak_ptr
C++11: unique_ptr、shared_ptr、weak_ptr
注:C++11中的unique_ptr与boost库中的scoped_ptr实现一致,均为防拷贝