个人主页:Lei宝啊
愿所有美好如期而遇
智能指针是什么?
智能指针(smart pointer)是一种用于自动管理动态分配内存的指针(其实也就是个类,在类中去使用一个指针管理资源)。
智能指针有多种实现方式,如:auto_ptr(狗都不用),unique_ptr,shared_ptr,weak_ptr
剩下的不再列出,使用他们需要包含头文件memory。
为什么需要智能指针?
为了解决内存泄漏的问题,你可能会说,小心一些,像C语言那样不就好了吗?C++因为引入了异常,所以在下面这种情况下没有智能指针就会出现内存泄漏:
int* a = new int;
int* b = new int;
//如果这个函数抛异常,被其他地方捕获,那么资源泄漏
//又或者在b处new失败,抛异常在其他地方异常被捕获,那么资源也会泄漏
func();
delete a;
delete b;
所以我们需要智能指针来替我们管理资源,这样,一但出了这个作用域,就会析构释放资源。
智能指针是怎么实现的?
智能指针需要满足两个条件:
- RAII(Resource Acquisition Is Initialization),也可以叫做资源获得立即初始化,利用对象生命周期来控制程序资源。
- 像指针一样
我们按照上面给出的几个智能指针挨个实现:
auto_ptr
这种智能指针比较粗暴,拷贝构造和赋值运算符重载就干一件事:一个资源只能被一个智能指针管着,如果没有智能指针管,那么释放资源。
拷贝构造:新智能指针去管,旧智能指针置空。
赋值重载:被赋值的智能指针释放掉他原来管着的资源,去管理赋值的智能指针管理的资源,并且这个智能指针置空。
析构:释放管理的资源。
template<class T>
class auto_ptr
{
public:
//RAII 资源获得立即初始化
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr& ptr)
{
_ptr = ptr._ptr;
ptr._ptr = nullptr;
}
auto_ptr& operator=(auto_ptr& ptr)
{
if (this != &ptr)
{
if (_ptr)
delete _ptr;
_ptr = ptr._ptr;
ptr._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "~auto_ptr" << endl;
delete _ptr;
}
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
unique_ptr
这个也比较粗暴,直接禁止拷贝与赋值,删除了他们的成员函数。
template<class T>
class unique_ptr
{
public:
//RAII 资源获得立即初始化
unique_ptr(T* ptr)
:_ptr(ptr)
{}
unique_ptr(unique_ptr& ptr) = delete;
unique_ptr& operator=(unique_ptr& ptr) = delete;
~unique_ptr()
{
if (_ptr)
{
cout << "~unique_ptr" << endl;
delete _ptr;
}
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
shared_ptr
但是如果要使用智能指针,而且一定要进行拷贝,那么前两种智能指针都不是好选择,这时我们就可以使用这个智能指针。
这个智能指针增加了一个引用计数,但是我们需要注意的是,同一个资源使用一个引用计数,不同资源使用的是不同的引用计数,也就是说,我们不能简单的就使用静态成员变量,这样是行不通的。
所以我们使用一个int*的指针,在构造时将他指向的值初始化为1,在拷贝构造时,新对象的int*指针同步被拷贝对象,并将值++;赋值操作时,如果被赋值对象引用计数为1,那么不仅*(int*)的值需要--,并且这个对象指向的资源还需要析构,之后的操作同拷贝构造。
并且,我们一定要注意的是,不能自己给自己赋值,因为当被赋值对象引用计数为1时,析构之后资源被释放,然后再进行对成员变量的操作时,那就坑了,因为这些资源已经被释放了。
template<class T>
class shared_ptr
{
public:
//RAII 资源获得立即初始化
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_count(new int(1))
{}
shared_ptr(shared_ptr& ptr)
{
_ptr = ptr._ptr;
_count = ptr._count;
(*_count)++;
}
shared_ptr& operator=(shared_ptr& ptr)
{
if (_ptr != ptr._ptr)
{
release();
_ptr = ptr._ptr;
_count = ptr._count;
(*_count)++;
}
return *this;
}
void release()
{
if (--(*_count) == 0)
{
cout << "~shared_ptr" << endl;
delete _ptr;
delete _count;
}
}
~shared_ptr()
{
release();
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int count()
{
return *_count;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _count;
};
weak_ptr
我们这里需要提到,shared_ptr有循环引用的问题:
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
return 0;
}
void release()
{
if (--(*_count) == 0)
{
cout << "~shared_ptr" << endl;
delete _ptr;
delete _count;
}
}
~shared_ptr()
{
release();
}
node2先进行析构,引用计数--,_count == 1, 什么也没发生:
接着就是node1进行析构,引用计数--,_count == 1, 同样什么也没有发生,也就导致,他们的资源都没有释放,并且他们的成员变量也没有调用析构函数,造成了资源泄漏。
所以这里我们引入了weak_ptr,这个智能指针只是单纯的指向资源,不对资源做释放。
template<class T>
class weak_ptr
{
public:
//RAII 资源获得立即初始化
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& ptr)
{
_ptr = ptr.get();
}
weak_ptr& operator=(const shared_ptr<T>& ptr)
{
_ptr = ptr.get();
return *this;
}
//error
/*~weak_ptr()
{
cout << "~weak_ptr" << endl;
if (_ptr)
delete _ptr;
}*/
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
所以我们可以修改成这样:
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
return 0;
}
这样就不会有循环引用的问题了。