1、为什么要使用智能指针?
C++的内存管理是让很多人头疼的事,一不小心就会发生内存泄漏,重复释放,野指针等问题。大部分关于指针的问题都是来源于堆空间,为什么呢?我们知道栈上的空间是由系统维护的,申请和释放的工作都是由系统根据栈的性质来完成的,不需要我们过多干预。而堆上空间的申请(new)和释放(delete)都必须由程序员显示的调用,并且很重要的一点,这段空间的生命周期就在new 和 delete 之间。但是我们不能避免程序还未执行到delete就跳转了,或者在函数中没有执行到delete语句就返回了,如果我们不在每一个可能跳转和返回之前释放内存,就会造成内存泄漏。所以程序员必须很仔细的申请并给出对应的释放语句,但是由于程序的复杂都增大,判断、循环、递归这样的语句会让程序的走向处于不定境地。很有可能出现内存泄露的问题。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。
2、什么是RAII?
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这样做有两个好处:
(1)我们不需要再显示的释放资源
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效
在C++中它被应用的实例除了这里的智能指针,还有C++11中的lock_guard 对互斥锁的管理也是典型的RAII机制。关于RAII模板化实现 ,参考下面的博客链接。
参考:https://blog.csdn.net/10km/article/details/49847271
但是我们要注意RAII != 智能指针,它只是解决问题的一种思想。智能指针顾名思义,它肯定要有指针的行为,因此我们还需要把* 和 -->进行重载。
3、有哪些智能指针?
1、auto_ptr
我们先来看它的简单使用
class Date
{
public:
Date()
{
cout << "Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
int year;
int month;
int days;
};
void test()
{
auto_ptr<Date> d1(new Date);
auto_ptr<Date> d2(d1);
//d1->year = 2019;
}
分析一下结果发现明明有连个对象却只构造了一次,析构也只有一次,执行test()函数中第一行代码肯定是会调用构造函数,所以结果打印第一行没问题,下一行代码用的d1拷贝构造d2,也没问题,然后出了函数作用域,要销毁对象,按照入栈顺序要先销毁d2,再销毁d1。但只打印了一次,这一次肯定是d2的,d1没有打印就会说明这个对象已经被销毁了。我们如果把上面注释的那一行代码放开,程序运行之后会发生右边这种情况。为什么会这样呢?这是由于auto_ptr的实现原理决定的,auto_ptr的实现原理就是管理权转移。当对象拷贝或者赋值后,前面的对象就悬空了 。下面模拟实现一份auto_ptr
template <class T>
class AutoPtr {
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{
cout << "AutoPtr() :" <<_ptr << endl;
}
~AutoPtr()
{
cout << "~AutoPtr() :" << _ptr<< endl;
if (_ptr)
delete _ptr;
}
//这里没有用const 因为要修改它
AutoPtr(AutoPtr<T>& s)
{
cout << "AutoPtr(T&ptr) : " << _ptr << endl;
_ptr = s._ptr;
//将原对象悬空
s._ptr = nullptr;
}
AutoPtr<T> operator=(AutoPtr<T>& s)
{
if (*this != s) {
//如果当前对象中有资源,释放
if (_ptr)
delete _ptr;
//将s中的资源转移到当前对象中
_ptr = s._ptr;
s._ptr = nullptr;
}
return *this;
}
T& operator* ()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
void TestAutoPtr()
{
AutoPtr<Date> p(new Date);
AutoPtr<Date> p2(p);
}
简单总结一下就是auto_ptr可用来管理单个对象的内存,控制权可以随意转换但只有一个在用,不要轻易使用operator=() 或者拷贝构造,使用了就不要再访问之前对象的数据。所以说它的一些设计也不太符合C++的编程设计,所以这个指针已经被C++淘汰了。
2、unique_ptr
unique_ptr 是一种简单粗暴的智能指针,既然你拷贝和赋值会造成很多问题,那就不要拷贝了。所以它是独享所有权的,不让你拷贝和赋值。下面是unique_ptr的模拟实现
using namespace std;
template <class T>
class UniquePtr {
public:
UniquePtr(T* ptr)
:_ptr(ptr)
{}
~UniquePtr()
{
if (_ptr)
delete _ptr;
}
//C++防拷贝方式
UniquePtr(UniquePtr<T> const& ) = delete;
UniquePtr<T> operator=(UniquePtr<T>const & ) = delete;
//C++98防拷贝方式 (只声明不定义)不能不写,它会用默认的,所以必须写
UniquePtr(UniquePtr<T> const&);
UniquePtr<T>operator=(UniquePtr<T> const &);
T& operator* ()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr
};
3、shared_ptr
auto_ptr有缺陷,unique_ptr不能拷贝,如果真的要实现拷贝,C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
shared_ptr的原理:是通过引用计数的方式实现对象共享资源。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
template <class T>
class SharedPtr {
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
,_pCount(new int(1))
,_pMutex(new mutex)
{
if (_ptr == nullptr)
_pCount = 0;
}
~SharedPtr()
{
Release();
}
//p(p1)
SharedPtr(const SharedPtr<T> & s)
: _ptr(s._ptr)
,_pCount(s._pCount)
,_pMutex(s._pMutex)
{
AddCount();
}
//p = p1;
SharedPtr<T>& operator=(const SharedPtr<T> & s)
{
if (this != &s) {
if(_ptr)
//释放管理的旧资源
Release();
_ptr = s._ptr;
_pCount = s._pCount;
// 如果是一个空指针对象,则不加引用计数,否则才加引用计数
if(_ptr)
AddCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int AddCount()
{
_pMutex->lock();
++(*_pCount);
_pMutex->unlock();
return *_pCount;
}
int SubCount()
{
_pMutex->lock();
--(*_pCount);
_pMutex->unlock();
return *_pCount;
}
int UseCount()
{
return *_pCount;
}
T* Get()
{
return _ptr;
}
private:
void Release()
{
if (_ptr && --(*_pCount) == 0) {
delete _ptr;
delete _pCount;
}
}
private:
T* _ptr;
int *_pCount; //引用计数
mutex* _pMutex;
};
void TestSharedPtr()
{
SharedPtr<int>sp1(new int(10));
SharedPtr<int>sp2(sp1);
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
SharedPtr<int> sp3(new int(10));
sp2 = sp3;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
cout << sp3.UseCount() << endl;
sp1 = sp3;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
cout << sp3.UseCount() << endl;
}
int main()
{
TestSharedPtr();
getchar();
return 0;
}