RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
在c++中,动态内存的管理式通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。有时使用完对象后,忘记释放内存,造成内存泄漏的问题。
所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
下面是智能指针的基本框架,所有的智能指针类模板中都需要包含一个指针对象,构造函数和析构函数。
template
class auto_ptr {
public:
auto_ptr(T* ptr )
: _ptr(ptr)
{}
~auto_ptr()
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
private:
T* _ptr;
};
智能指针的使用跟普通指针类似,可以使用运算符“ * " 和 ” -> "去获得指向的对象,因此,我们就需要在类中重载" * " 和" -> "函数。
template
class auto_ptr {
public:
auto_ptr(T* ptr )
: _ptr(ptr)
{}
~auto_ptr()
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
T& operator*() { return *_ptr; }
T& operator->() { return _ptr; }
private:
T* _ptr;
};
当程序结束时,此时ptr1和ptr2指针被销毁时,对象ptr1和ptr2会自动调用析构函数去释放所指向的资源,这是智能指针特点。
由于我的类中没有定义拷贝构造函数和赋值重载函数,那么我们只能调用类中原生的拷贝构造函数和赋值重载函数。那么就会程序就会出现崩溃的问题,如下:
ptr2和ptr1指向的同一块空间,当ptr2被销毁时,它会调用它的析构函数去delete该资源对象,当ptr1被销毁时,也会去调用它的析构函数去释放ptr1所指向的资源.所以,当程序结束时,ptr2被先被销毁,同时释放ptr2所指向的资源,然后ptr1被销毁,也去释放该资源对象,那么如下的资源对象同时被释放两次,所以程序就会被崩溃掉。(资源对象被释放后,如果再去释放该资源,程序就会崩溃)
综上所述,我们不能使用原生的拷贝构造函数和赋值重载函数,并且定义的拷贝构造函数和赋值重载函数需要考虑只能释放一次资源对象。
auto_ptr是c++98版本库中提供的智能指针,该指针解决上诉的问题采取的措施是管理权转移的思想,也就是原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。
如果auto_ptr调用拷贝构造函数或者赋值重载函数后,如果再去使用原来的对象的话,那么整个程序就会崩溃掉(因为原来的对象被设置为nullptr),这对程序是有很大的伤害的.所以很多公司会禁用auto_ptr智能指针。
auto_ptr的拷贝构造函数和赋值重载函数的实现
//管理权转移
auto_ptr(auto_ptr& sp)//auto_ptr的拷贝构造函数
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
auto_ptr operator=( auto_ptr& sp)//auto_ptr的赋值重载函数
{
if (_ptr = sp._ptr) {
_ptr = sp._ptr;
sp._ptr = NULL;
}
return _ptr;
}
unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。
#include
#include
using namespace std;
int main()
{
unique_ptr sp1(new int);
unique_ptr sp2(sp1);//不能用unique_ptr拷贝构造;
unique_ptr sp3(new int);
sp3 = sp1;//不能用unique_ptr拷贝构造;
return 0;
}
下面的代码是简单模拟实现了一下它的原理 :
template
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr& sp) = delete;
unique_ptr& operator=(const unique_ptr& sp) = delete;
private:
T* _ptr;
};
share_ptr是c++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,因此是程序不会崩溃掉。
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
引用计数是用来记录资源对象中有多少个指针指向该资源对象。
赋值重载的三种情况:
SmartPtr.h:
namespace ayf1
{
template
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pCount(new int(1))
{}
void Release()
{
if (--(*_pCount) == 0)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
delete _pCount;
}
}
~shared_ptr()
{
Release();
}
// sp1(sp2)
shared_ptr(const shared_ptr& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
{
(*_pCount)++;
}
// sp1 = sp5
// sp1 = sp1
shared_ptr& operator=(const shared_ptr& sp)
{
//if (this == &sp)
if (_ptr == sp._ptr)
{
return *this;
}
// 减减被赋值对象的计数,如果是最后一个对象,要释放资源
/*if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}*/
Release();
// 共管新资源,++计数
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
// 引用计数
int* _pCount;
};
}
test.cpp:
void test_shared_ptr1()
{
ayf1::shared_ptr sp1(new A);
ayf1::shared_ptr sp2(sp1);
ayf1::shared_ptr sp3(sp1);
sp1->_a1++;
sp1->_a2++;
cout << sp2->_a1 << ":" << sp2->_a2 << endl;
sp2->_a1++;
sp2->_a2++;
cout << sp1->_a1 << ":" << sp1->_a2 << endl;
ayf1::shared_ptr sp5(new A);
ayf1::shared_ptr sp6(sp5);
sp1 = sp5;
sp2 = sp5;
sp3 = sp5;
// 自己给自己赋值
ayf1::shared_ptr sp4(new int);
sp4 = sp4;
sp1 = sp5;
}
int main()
{
test_shared_ptr1();
return 0;
}
运行结果:
shared_ptr固然好用,但是它也会有问题存在。假设我们要使用定义一个双向链表,如果我们想要让创建出来的链表的节点都定义成shared_ptr智能指针,那么也需要将节点内的_pre和_next都定义成shared_ptr的智能指针。如果定义成普通指针,那么就不能赋值给shared_ptr的智能指针。
当其中两个节点互相引用的时候,就会出现循环引用的现象。如下:
use_count(): 返回智能指针对象的引用计数。
那么如何解决这个shared_ptr的循环引用呢?
weak_ptr对象指向shared_ptr对象时,不会增加shared_ptr中的引用计数,因此当node1销毁掉时,则node1指向的空间就会被销毁掉,node2类似,所以weak_ptr指针可以很好解决循环引用的问题。
所以在定义双向链表或者在二叉树等有多个指针的时候,如果想要将该类型定义成智能指针,那么结构体内的指针需要定义成weak_ptr类型的指针,防止循环引用的出现。
// 简化版本的weak_ptr实现
template
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr& sp)
:_ptr(sp.get())
{}
weak_ptr& operator=(const shared_ptr& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
当我们释放一个指向数组的指针的时候,delete[]后面的空方括号是必须存在(如下),它指示编译器此指针指向的是一个对象数组的第一个元素,如果我们在delete一个指向数组的指针中忽略了方括号,我们的程序可能在执行过程中在没有任何警告下行为异常。
我们如果在动态内存中创建出一个数组,用一个shared_ptr对象去指向该数组,当shared_ptr使用完后,就会去调用析构函数,由于shared_ptr默认的删除方式是 delete ptr,后面没有带方括号,那么程序就会崩掉
如果我们打开一个了文件,返回一个文件指针,让一个shared_ptr对象去指向该文件,那么在调用析构函数的时候就不能采用delete方法,而是使用flose()函数去关闭该文件。
因此,shared_ptr 类中提供了一个构造函数可以自定义一个删除器去指定析构函数的删除方式。
这个自定义删除器可以是函数指针,仿函数,lamber,包装器。
shared_ptr中的析构函数会去调用DelArry仿函数去释放动态数组。
感谢阅读!!!!!!!!!!!!!!