智能指针
本人不才,只能将智能指针介绍一下,无法结合线程进行深入探索
介绍及作用
在异常产生进行跳转时,通过栈帧回收进行内存释放,防止内存泄漏
基于RAII
思想可以创建出只能指针
RAII(Resource Acquisition Is Initialization)——是一种利用对象
控制空间生命周期的技术
这种技术好处就在于可以自动化的释放资源
智能指针——使用如指针,支持->和*
,*针对内置数据类型
,->针对自定义数据类型
先介绍一个对象管理一份资源
auto_ptr
template<class T>
class auto_ptr
{
// auto_ptr 会产生指针悬空的情况,在进行解引用的时候很危险
public:
auto_ptr(T* p=nullptr) :_ptr(p) {}
~auto_ptr()
{
puts("~auto_ptr()");
delete _ptr;
}
auto_ptr(auto_ptr<T>& p)
{
_ptr = p._ptr;
p._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& p)
{
// 因为是一个对象管理一份资源,比较的时候也可以使用this!=&p
if (_ptr != p._ptr)
{
if (_ptr) delete _ptr;
_ptr = p._ptr;
p._ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
auto_ptr会在赋值的时候将资源进行转移,并将自己置为nullptr,从而造成指针悬空的问题
unique_ptr
template<class T>
class unique_ptr
{
// 在auto_ptr的基础上进行优化,防拷贝
public:
unique_ptr(T* p = nullptr) :_ptr(p) {}
~unique_ptr()
{
puts("~auto_ptr()");
delete _ptr;
}
unique_ptr(const unique_ptr<T>& p) = delete;
unique_ptr& operator=(const unique_ptr<T>& p) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
// unique_ptr(const unique_ptr<T>& p);
// unique_ptr& operator=(const unique_ptr<T>& p);
T* _ptr;
};
只是为了解决拷贝带来的指针悬空的问题——
禁用拷贝构造和赋值重载
有两种
方式达到禁用拷贝构造和赋值重载
- 将拷贝构造和赋值重载
定义成private
- 由于
C++11
扩展了delete
的功能,可以在public
中对两个函数使用delete关键字修饰
多个指针同时享有一份资源
shared_ptr
使用一个计数指针进行计数
我的代码写成这个样子是因为考虑到可能只声明指针但是没有赋值的情况,所以就需要对指针位nullptr的情况进行特殊判断
struct ListNode
{
int val;
bit::shared_ptr<ListNode> next;
bit::shared_ptr<ListNode> prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
template<class T>
class shared_ptr
{
// 删除器就是为了解决:释放数组,不是new出来的指针
public:
shared_ptr(T* p = nullptr) :_ptr(p), _count(new int(0)) {}
template<class D>
shared_ptr(T* p,D del)
:_ptr(p),
_count(new int(1)),
_del(del)
{
if (p) (*_count)++;
}
~shared_ptr()
{
if (_ptr)
{
//printf("~shared_ptr() -> %p\n", _ptr);
if (--(*_count) == 0)
{
delete _count;
}
delete _ptr;
}
if (_ptr == nullptr) delete _count;
}
shared_ptr(const shared_ptr<T>& p)
{
if (_ptr && _ptr != p._ptr)
{
if (--(*_count) == 0) _count = p._count;
(*_count)++;
_ptr = p._ptr;
}
else
{
// nullptr / 有值相等
_ptr = p._ptr;
_count = p._count;
//*_count++; // ++优先级大于*,最好不要写这种代码
(*_count)++;
}
}
shared_ptr& operator=(const shared_ptr<T>& p)
{
if (_ptr && _ptr != p._ptr)
{
if (--(*_count) == 0) _count = p._count;
(*_count)++;
_ptr = p._ptr;
}
else
{
// nullptr / 有值相等
_ptr = p._ptr;
_count = p._count;
(*_count)++;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count() const
{
return *_count;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _count;// 引入计数指针
function<void(T*)> _del = [](T* p) {delete p; };
};
这里会产生循环引用的问题
为了解决这个问题有了weak_ptr
weak_ptr
weak_ptr只进行引用不进行计数
struct ListNode
{
int val;
bit::weak_ptr<ListNode> next;
bit::weak_ptr<ListNode> prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
template<class T>
class weak_ptr
{
// 不增加引用计数
public:
weak_ptr() :_ptr(nullptr) {}
~weak_ptr()
{
//printf("~shared_ptr() -> %p\n", _ptr);
}
weak_ptr(const shared_ptr<T>& p)
{
_ptr = p.get();
}
weak_ptr& operator=(const shared_ptr<T>& p)
{
_ptr = p.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
使用包装器进行释放
这里还有一个问题——如何根据空间开的个数进行释放呢,数组和指针释放的方式是不一样的
也就是在share_ptr
看不懂的版本
template<class T>
struct Del
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
template<class T>
class shared_ptr
{
// 删除器就是为了解决:释放数组,不是new出来的指针
public:
shared_ptr(T* p = nullptr) :_ptr(p), _count(new int(0)) {}
template<class D>
shared_ptr(T* p,D del)
:_ptr(p),
_count(new int(1)),
_del(del)
{
if (p) (*_count)++;
}
~shared_ptr()
{
if (_ptr)
{
//printf("~shared_ptr() -> %p\n", _ptr);
if (--(*_count) == 0)
{
delete _count;
}
delete _ptr;
}
if (_ptr == nullptr) delete _count;
}
shared_ptr(const shared_ptr<T>& p)
{
if (_ptr && _ptr != p._ptr)
{
if (--(*_count) == 0) _count = p._count;
(*_count)++;
_ptr = p._ptr;
}
else
{
// nullptr / 有值相等
_ptr = p._ptr;
_count = p._count;
//*_count++; // ++优先级大于*,最好不要写这种代码
(*_count)++;
}
}
shared_ptr& operator=(const shared_ptr<T>& p)
{
if (_ptr && _ptr != p._ptr)
{
if (--(*_count) == 0) _count = p._count;
(*_count)++;
_ptr = p._ptr;
}
else
{
// nullptr / 有值相等
_ptr = p._ptr;
_count = p._count;
(*_count)++;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count() const
{
return *_count;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _count;// 引入计数指针
function<void(T*)> _del = [](T* p) {delete p; };
};
仿函数,函数指针,lambda可以使用包装器
——不论使用哪一种,实现的目的就是为了释放数组
他的类型一定是void(*T)
为什么需要在声明的时候就给默认到lambda
——如果在这个指针是拷贝构造
生成的,那还得进行包装器拷贝,同样在赋值重载
的地方也需要同样的操作,还不如声明的时候给默认值