智能指针
GC–garbage collection垃圾回收,Java里的机制。在头文件<memory>
中
内存泄漏
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
采用RAII思想或者智能指针来管理资源。
RAII资源获得即初始化
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
相当于把资源生命周期和对象生命周期绑定在一起了==>在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:1.不需要显式地释放资源;2.采用这种方式,对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
//保存资源
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
//释放资源
~SmartPtr()
{
if(_ptr) delete _ptr;
}
private:
T* _ptr;
};
上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。
C98 auto_ptr
原理如下:
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为。
AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。
template<class T>
class auto_ptr {
public:
//保存资源
auto_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
//释放资源
~auto_ptr()
{
if(_ptr) delete _ptr;
}
//重载operator*
T& operator*()
{
return *_ptr;
}
//重载operator->
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
存在的问题-对象悬空
auto_ptr类中没有写拷贝构造,构造函数就是浅拷贝,auto_ptr<int> auto_p1(new int); auto_ptr<int> autop2(auto_p2);
这样的代码,就会造成资源重复析构。C98中对于auto_ptr
资源重复析构提出的解决方法是资源管理器转移,即只有一个对象管一份资源,auto_p1
就不管了,让auto_p2
管==>会导致对象悬空,即auto_p1悬空。
auto_ptr(SmartPtr<T>& sp) :_ptr(sp.ptr) {
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(SmartPtr<T>& sp) {
// 检测是否为自己给自己赋值
if (this != &sp) {
// 释放当前对象中资源
if (_ptr) delete _ptr;
// 转移sp中资源到当前对象中
_ptr = sp._ptr;
sp._ptr = NULL;
}
return *this;
}
注意:解决办法绝对不能写深拷贝!!因为要模拟的是原生指针,且这份资源归属是用户的,咱不能偷偷地copy一份。
C++11 unique_ptr
借鉴的是boost的scoped_ptr(防拷贝的智能指针),只是在auto_ptr的基础上加了两句。详细的模拟实现请参考我的gitee库
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
C++11 shared_ptr
shared_ptr本身是线程安全的,用锁保护了引用计数的++/–,但它管理的资源不是线程安全的,需要用户手动控制。共享指针的mutex保护的是自己的计数器的线程安全,不保护资源的线程安全。
借鉴的是boost的shared_ptr(可以拷贝的智能指针),原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
- shared_ptr在其内部,给每个资源都维护了着一份计数器,用于记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
计数器的实现
可以采用 static map<T* ptr, static int count>;
,但是后续加锁是个大问题。
简单的方法就是申请新资源的时候再开一块计数器空间,让对象中的一个指针指向资源,另个指针指向计数器空间。
成员变量的计数器要写成int* count;
,可以实现对单份资源的管理,让一份资源拥有一个计数器。
不能写int count;
这个变成了单个对象的计数;
也不能写static int count;
因为静态成员变量是属于整个类的,所有对象均可以访问,相当于公共计数器,不是独一份资源的计数器。
锁的实现
一份资源对应一把锁,故写成mutex* _mutex;
在用shared_ptr的时候,如何保证资源的线程安全?**重新用一把锁保护资源!**注意此处不能去用计数器的锁,因为两块资源不是一样的空间。
存在的问题-循环引用
在如下所示struct ListNode
对象创建的双向链表处会出问题,因为其成员变量是std::shared_ptr<ListNode>
类型的,node1
本身和node2._prev
会同时指向node1的引用计数,node2
本身和node1._next
会同时指向node2的引用计数。
node1和node2都是局部对象,出了作用域就会销毁,但是他们的_prev
和_next
没法对引用计数器–。类对象的成员什么时候销毁?对象销毁的时候,其成员变量才会销毁,但是node2要等node1._next
销毁了才会销毁,node1要等node2._prev
销毁了才会销毁,这就造成了死循环,谁都没有销毁。
拓展来说,假设有两个类,类A中有个成员管理着类B的另一个成员,类B中有个成员管理着类A的另一个成员,这也会造成循环引用。
struct ListNode
{
std::shared_ptr<ListNode> _prev;
std::shared_ptr<ListNode> _next;
~ListNode() {std::cout << "~ListNode()" << std::endl; }//仅为观察实验现象,无其他作用
};
int main()
{
std::shared_ptr<ListNode> node1(new ListNode);
std::shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;//赋值构造 会对node1的引用计数做++
node2->_prev = node1;//赋值构造 会对node2的引用计数做++
return 0;
}
只要屏蔽node1->_next = node2;
或者node2->_prev = node1;
里的任何一句,都不会有循环引用的问题。
C++11 weak_ptr
没有使用RAII思想。其功能是可以指向资源/访问资源,但是不参与资源的管理,不增加引用计数。与shared_ptr搭配使用。
struct ListNode
{
std::weak_ptr<ListNode> _prev;//使用weak_ptr来解决
std::weak_ptr<ListNode> _next;//使用weak_ptr来解决
~ListNode() {std::cout << "~ListNode()" << std::endl; }//仅为观察实验现象,无其他作用
};
int main()
{
std::shared_ptr<ListNode> node1(new ListNode);
std::shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;//赋值构造 会对node1的引用计数做++
node2->_prev = node1;//赋值构造 会对node2的引用计数做++
return 0;
}
C++11 default_delete
以上4种指针只能管理单个new空间,因为delete在释放资源的时候,需要考虑到是数组(要用delete[])还是单个数(delete即可),new[]
和delete[]
要匹配,否则可能会导致问题。
于是C++11引入定制删除器,默认的定制删除器用的是delete。constexpr shared_ptr() noexcept;
用到定制删除器可以看这个构造函数template <class U, class D> shared_ptr (U* p, D del);
,其中del可以用函数指针、仿函数、lambda表达式。
template<class T>
struct delete_array
{
operator()(const T* ptr)
{
delete[] ptr;
cout << "delete[] :" << ptr << endl;
}
};
int main()
{
std::shared_ptr<int> sp1(new int[10], delete_array<int>());
//仿函数
std::shared_ptr<string> sp2(new string[10], delete_array<string>());
//lambda表达式
std::shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });
std::shared_ptr<FILE> sp4(fopen("text.txt", "r"), [](FILE* ptr) {fclose(ptr); });
return 0;
}
结合定制删除器改造shared_ptr
template<class T>
struct defaule_delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T, class D = defaule_delete<T>>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pRefCount(new int(1)), _mutex(new mutex)
{}
void Release()
{
int flag = 0;//标记是否需要释放锁,这是局部变量(独立栈空间)
_mutex->lock();
if (--(*_pRefCount) == 0)
{
flag = 1;
//delete _ptr;
_del(_ptr);
delete _pRefCount;
}
_mutex->unlock();
if (flag) delete _mutex;
}
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pRefCount(sp._pRefCount), _mutex(sp._mutex)
{
_mutex->lock();
++(*_pRefCount);
_mutex->unlock();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (sp._ptr != _ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_mutex = sp._mutex;
_mutex->lock();
++(*_pRefCount);
_mutex->unlock();
}
return *this;
}
int use_count() { return *_pRefCount; }
T* get()
{
return _ptr;
}
// 像指针一样使用
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
T* get() const { return _ptr; }
private:
T* _ptr;
int* _pRefCount;
mutex* _mutex;
D _del;
};
template<class T>
struct delete_array
{
void operator()(const T* ptr)
{
delete[] ptr;
//cout << "delete[]" << endl;
}
};
struct close_file
{
void operator()(FILE* ptr)
{
fclose(ptr);
}
};
int main()
{
shared_ptr<int, delete_array<int>> sp1(new int[5]);
shared_ptr<string, delete_array<string>> node1(new string[5]);
shared_ptr<FILE, close_file> file1(fopen("test.txt", "r"));
//shared_ptr<int> sp2(new int);
return 0;
}