文章目录
一、什么是智能指针
#include<iostream>
using namespace std;
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
// 如果出了异常,会直接跳转到捕获的地方去了。
int* p1 = new int;
int* p2 = new int;
cout << div() << endl;
//如果是p1 抛异常需要释放p2和div
//如果是p2 抛异常需要释放p1和div
//如果是div抛异常,需要释放p1和p2
delete p1;
delete p2;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
RAII思想
RAII(Resource Acquisition Is Initialization)(资源请求即初始化,也就是获取到资源,马上就初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
①不需要显式地释放资源。
②采用这种方式,对象所需的资源在其生命期内始终保持有效
#include<iostream>
using namespace std;
//利用RAII思想设计的delete资源的类
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr(){
cout<<"delete"<<endl;
delete _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(new int);
cout << div() << endl;
cout<<"释放资源"<<endl;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
不管是正方func结束,还是抛异常,sp1和sp2都会调用析构函数,释放资源。
但是我们这里就不能通过解引用的方式获取到资源了,这里我们就需要通过运算符重载来获取到我们的资源。
#include<iostream>
using namespace std;
//利用RAII思想设计的delete资源的类
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr(){
cout<<"delete"<<endl;
delete _ptr;
}
T&operator*()
{
return *_ptr;
}
T&operator->()
{
return *_ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(new int);
cout << div() << endl;
*sp1=1;
*sp2=2;
cout<<*sp1<<endl;
cout<<*sp2<<endl;
cout<<"释放资源"<<endl;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
1.利用RAII思想设计delete类
2.重载*和->运算符
std::auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
#include<iostream>
#include <memory>
using namespace std;
class A
{
public:
~A(){
cout<<"~A()"<<endl;
}
private:
int _a1;
int _a2;
};
int main()
{
auto_ptr<A>ap1(new A);
return 0;
}
#include<iostream>
#include <memory>
using namespace std;
class A
{
public:
~A(){
cout<<"~A()"<<endl;
}
//private:
int _a1=0;
int _a2=0;
};
int main()
{
auto_ptr<A>ap1(new A);
ap1->_a1++;
ap1->_a2++;
return 0;
}
二、智能指针的拷贝问题(C++98)
因为智能指针在拷贝的时候,只有一个内置类型的拷贝,所以发生的是浅拷贝,会析构同一块空间,而二次析构内存就会报错,也就是我们的浅拷贝问题
#include<iostream>
#include <memory>
using namespace std;
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
class A
{
public:
~A(){
cout<<"~A()"<<endl;
}
//private:
int _a1=0;
int _a2=0;
};
int main()
{
SmartPtr<A>sp1(new A);
sp1->_a1++;
sp1->_a2++;
SmartPtr<A>sp2(sp1);
return 0;
}
解决方案:
1.深拷贝?不能,因为违背了功能需求。智能指针只是像指针一样,帮你托管空间。
但是像下面这种情况我们就需要的是浅拷贝。
list<int> lt;
auto it=lt.begin();
为什么迭代器浅拷贝没有问题呢?
因为迭代器并不负责资源的管理!迭代器只是为了封装底层的细节,以统一的方式来遍历资源。它不管迭代器中资源的释放。
#include<iostream>
#include <memory>
using namespace std;
class A
{
public:
~A(){
cout<<"~A()"<<endl;
}
//private:
int _a1=0;
int _a2=0;
};
int main()
{
auto_ptr<A>sp1(new A);
sp1->_a1++;
sp1->_a2++;
auto_ptr<A>sp2(sp1);
return 0;
}
库里面的是没有问题的。
因为库里面的行为就是将sp1的资源转移给了sp2。
是资源管理权转移,不负责任地拷贝,会导致被拷贝对象的悬空问题
0.auto_ptr
手动实现一个auto_ptr,了解上述的C++98的智能指针的底层
#include <iostream>
#include <string>
using namespace std;
namespace zhuyuan
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
~auto_ptr(){
cout<<"delete"<<endl;
delete _ptr;
}
auto_ptr(auto_ptr<T>&ap)
:_ptr(ap._ptr)
{
ap._ptr=nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if(this!=&ap)
{
if(_ptr)
{
delete _ptr;
}
_ptr=ap._ptr;
ap._ptr= nullptr;
}
return *this;
}
T&operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
class A
{
public:
~A()
{
cout<<"~A()"<<endl;
}
//private:
int _a1=0;
int _a2=0;
};
int main()
{
zhuyuan::auto_ptr<A> ap1(new A);
ap1->_a1++;
ap1->_a2++;
cout<<ap1->_a1<<" "<<ap1->_a2<<endl;
zhuyuan::auto_ptr<A> ap2(ap1);
return 0;
}
这个时候如果我们再去调用ap1指针,我们的程序就会发生崩溃。
很多公司明确要求不能使用C++98的智能指针!
boost库
智能指针首先从boost社区中发展起来的。
scoped_ptr
shared_ptr
weak_ptr
C++11
unique_ptr
shared_ptr
weak_ptr
1.unique_ptr
不允许拷贝,只要有拷贝就会报错
void test_unique_ptr()
{
std::unique_ptr<A> up2(new A);
std::unique_ptr<A> up1(up2);
}
手动实现
namespace zhuyuan
{
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~ unique_ptr(){
cout<<"delete"<<endl;
delete _ptr;
}
//防止拷贝C++11
unique_ptr( unique_ptr<T>&ap)=delete;
unique_ptr<T>& operator=( unique_ptr<T>& ap)=delete;
//防止拷贝C++98
//只声明,不实现
T&operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
void test_unique_ptr()
{
zhuyuan::unique_ptr<A> up2(new A);
zhuyuan::unique_ptr<A> up1(up2);
up1->_a1++;
up1->_a2++;
zhuyuan::unique_ptr<A>up3=up2;
}
unique_ptr:简单粗暴,不让拷贝,只适用于不需要拷贝的一些场景
那如果我们就是需要拷贝呢?
2.shared_ptr
void test_shared_ptr()
{
std::shared_ptr<A> up2(new A);
std::shared_ptr<A> up1(up2);
up1->_a1++;
up1->_a2++;
cout<<up1->_a1<<" "<<up1->_a2<<endl;
shared_ptr<A>up3=up2;
}
也就是说,它们共同管理了同一份数据。也就是三个指针指向的都是up2中的资源。
想要实现的话,我们就需要引用计数。
因为多个对象管理同一份资源,析构的时候就会出问题。但是,如果我们这时引入一个计数,也就是表示当前有多少个对象正在管理这份资源。当对象被析构的时候,我们就将这个计数–,当有新的对象引用这份资源的时候,我们就将这个计数++,当最后一个析构的对象释放时,释放这份资源。也就是说只需要析构一次就可以了。
这里使用静态计数对象是不可以滴。
因为一个资源就需要配一个计数,多个智能指针对象共管。
如果是静态对象的话,是所有资源都是有一个计数,因为静态成员属于整个类,类的所有对象。
每个资源需要管理时,会给构造函数,构造new一个计数。
namespace zhuyuan
{
template<class T>
class shared_ptr
{
public:
void Release()
{
//如果这里的引用计数先--,然后等于0了之后,也就是说没有对象使用这份资源了
//并且我们的要析构的资源的指针不为nullptr的时候
//我们就进行析构。
//同时清空我们这里的计数和我们的指向的资源。
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
// RAII思想
shared_ptr(T* ptr)
//使用初始化列表进行初始化
//初始化我们这里的资源
:_ptr(ptr)
//初始化我们的计数
//创建一个类对象的时候,默认计数就是1
, _pCount(new int(1))
{}
~shared_ptr()
{
//调用release进行析构
Release();
}
//拷贝构造
//sp1(sp2)
shared_ptr(const shared_ptr<T>& sp)
//将指针和计数都进行拷贝
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
//将计数++,表示又有一个对象对其进行了引用
(*_pCount)++;
}
//赋值
// sp1 = sp3
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
//防止自己给自己赋值
if (_ptr != sp._ptr)
{
//这里我们需要先把我们sp1的对象的资源的计数给释放掉
//然后再将sp1拷贝给sp3
//否则我们sp1所指向的资源的计数就会比真实的多出一份
//就会导致内存泄漏的问题!!
Release();
//将指针赋值
_ptr = sp._ptr;
//将计数也进行赋值
_pCount = sp._pCount;
//将计数++
++(*_pCount);
}
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
//指向的资源的地址
T* _ptr;
//引用计数
int* _pCount;
};
}
class A
{
public:
~A()
{
cout<<"~A()"<<endl;
}
//private:
int _a1=0;
int _a2=0;
};
void test_shared_ptr()
{
zhuyuan::shared_ptr<A> up2(new A);
zhuyuan::shared_ptr<A> up1(up2);
up1->_a1++;
up1->_a2++;
cout<<up1->_a1<<" "<<up1->_a2<<endl;
zhuyuan::shared_ptr<A>up3=up2;
zhuyuan::shared_ptr<int>up4(new int);
zhuyuan::shared_ptr<A> sp5(new A);
zhuyuan::shared_ptr<A> sp6(sp5);
}
重点注意
这里的赋值问题
// sp1 = sp3
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//防止自己给自己赋值
//如果自己给自己赋值,首先就会释放自己的资源,然后再进行赋值
//就会崩溃。
//if (this != &sp)
//采用下面的方法更好,这样我们知道如果我们拷贝的对象和我们被拷贝的对象
//所持有的是同一块空间的话,我们就不会进行拷贝。
//比方说我们的sp1和sp2指向的是同一块资源
//然后我们这里运行的时候,知道了sp1的ptr和sp2的ptr指向的是同一块空间
//就不再会进行拷贝。
if (_ptr != sp._ptr)
{
// 我们这里拿sp1 = sp3距离
//因为我们的sp1之后不再持有原来的资源了,而是指向sp3的资源
//这里我们需要先把我们sp1的对象的资源的计数给释放掉
//然后再将sp3拷贝给sp1
//否则我们原先sp1所指向的资源的计数就会比真实的多出一份
//就会导致内存泄漏的问题!!
Release();
//共同管理新的资源,++计数
//将指针赋值
_ptr = sp._ptr;
//将计数也进行赋值
_pCount = sp._pCount;
//将计数++
++(*_pCount);
}
return *this;
}
void Release()
{
//--被赋值对象的计数,如果是最后一个对象,需要释放资源
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
shared_ptr的问题
如果两个智能指针指向同一块资源给多线程用的时候,里面可能会存在线程安全的风险。
循环引用的问题
struct Node
{
int _val;
//自定义类型的对象不能赋值给原生指针,
//我们需要将其转换成智能指针
std::shared_ptr<Node> _next;
std::shared_ptr<Node> _prev;
~Node()
{
cout<<"~Node"<<endl;
}
};
void test_shared_ptr2()
{
//不支持隐式类型转换,所以我们需要使用()来进行赋值
std::shared_ptr<Node> n1(new Node);
std::shared_ptr<Node> n2(new Node);
//循环引用问题
n1->_next=n2;
n2->_prev=n1;
}
int main()
{
test_shared_ptr2();
return 0;
}
没有打印析构的信息,我们的资源没有被正确释放,这就出现了内存泄漏
如何理解这个问题?
当这个函数(test_shared_ptr2)结束了之后n2先析构,n1再析构,因为n2后定义,先析构
然后就结束了。
我们的两个结点都没有正常释放!!
进一步分析
n1的_next管着右边节点的内存块,n2的_prev管着左边结点的内存块。
所以n1的_next析构,右边的结点就释放了(delete),
n2的_next析构,左边的结点就释放了(delete)。
那么_next什么时候释放呢?_prev什么时候释放呢?
右边结点什么时候delete呢?
左边的结点被delete,调用析构函数,_next作为成员才会析构
左边结点什么时候delete呢?
右边的结点被delete,调用析构函数,_prev作为成员才会析构
这就是一个循环等待对方释放的过程!
share_ptr内部无法解决这个问题!所以我们需要使用weak_ptr!
3.weak_ptr
weak_ptr不是常规智能指针,没有RAII,不支持直接管理资源
weak_ptr主要用shared_ptr构造,用来解决shared_ptr的循环引用的问题
可以使用shared_ptr进行构造,为了不增加引用计数,也就是不参与资源管理
struct Node
{
int _val;
std::weak_ptr<Node> _next;
std::weak_ptr<Node> _prev;
~Node()
{
cout<<"~Node"<<endl;
}
};
void test_shared_ptr2()
{
//不支持隐式类型转换,所以我们需要使用()来进行赋值
std::shared_ptr<Node> n1(new Node);
std::shared_ptr<Node> n2(new Node);
n1->_next=n2;
n2->_prev=n1;
}
int main()
{
test_shared_ptr2();
return 0;
}
这里当_next和_prev是weak_ptr的时候,它不参与资源的释放管理,但是可以访问和修改资源,不增加计数,不存在循环引用的问题。
可以使用use_count来看一下这里使用weak_ptr前后的计数是多少
void test_shared_ptr2()
{
//不支持隐式类型转换,所以我们需要使用()来进行赋值
std::shared_ptr<Node> n1(new Node);
std::shared_ptr<Node> n2(new Node);
cout<<n1.use_count()<<endl;
cout<<n2.use_count()<<endl;
//自定义类型的对象不能赋值给原生指针
n1->_next=n2;
n2->_prev=n1;
cout<<n1.use_count()<<endl;
cout<<n2.use_count()<<endl;
}
实现一个weak_ptr
namespace zhuyuan
{
template<class T>
class shared_ptr
{
public:
void Release()
{
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
// RAII思想
shared_ptr(T* ptr)
:_ptr(ptr)
//创建一个类对象的时候,默认计数就是1
, _pCount(new int(1))
{}
~shared_ptr()
{
Release();
}
//拷贝构造
//sp1(sp2)
shared_ptr(const shared_ptr<T>& sp)
//将指针和计数都进行拷贝
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
//将计数++,表示又有一个对象对其进行了引用
(*_pCount)++;
}
// sp1 = sp3
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
//防止自己给自己赋值
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
private:
T* _ptr;
int* _pCount;
};
//辅助形智能指针,使命就是解决配合shared_ptr的循环引用问题。
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{
}
weak_ptr(const shared_ptr<T>&sp)
:_ptr(sp.get())
{
}
weak_ptr(weak_ptr<T>&sp)
:_ptr(sp._ptr)
{
}
weak_ptr<T> operator=(const shared_ptr<T>&sp)
{
_ptr=sp.get();
return * this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
public:
T* _ptr;
};
}
class A
{
public:
~A()
{
cout<<"~A()"<<endl;
}
//private:
int _a1=0;
int _a2=0;
};
struct Node
{
int _val;
zhuyuan::weak_ptr<Node> _next;
zhuyuan::weak_ptr<Node> _prev;
~Node()
{
cout<<"~Node"<<endl;
}
};
测试代码
void test_shared_ptr2()
{
//不支持隐式类型转换,所以我们需要使用()来进行赋值
zhuyuan::shared_ptr<Node> n1(new Node);
zhuyuan::shared_ptr<Node> n2(new Node);
cout<<n1.use_count()<<endl;
cout<<n2.use_count()<<endl;
//自定义类型的对象不能赋值给原生指针
n1->_next=n2;
n2->_prev=n1;
cout<<n1.use_count()<<endl;
cout<<n2.use_count()<<endl;
}
int main()
{
test_shared_ptr2();
return 0;
}
三、智能指针的空间释放问题
定制删除器
如果我们这里的智能指针new出来的不是一个对象,而是[]类型的,就会出现问题
struct Node
{
int _val;
zhuyuan::weak_ptr<Node> _next;
zhuyuan::weak_ptr<Node> _prev;
~Node()
{
cout<<"~Node"<<endl;
}
};
shared_ptr<Node> n1(new Node[5]);
shared_ptr<Node> n2(new Node);
但是对于内置类型是没有问题的。
shared_ptr<int> n1(new int[5]);
如果是new node[5]的话,底层就是malloc整一个node[5]的空间大小,然后调用5次构造函数
delete[] 5次析构函数+free
但是,这里new int[5]的时候,你告诉了编译器要构造5次,但是delete[]中并没有告诉编译器析构的次数。所以编译器会在开辟的空间中多开辟4个字节,来记录需要调用多少次析构函数
所以使用(char*)ptr-4来找到需要析构的次数5,然后进行析构
但是如果是delete的话,就直接将ptr所指向位置给析构了,不能正确地释放空间,所以编译会崩溃。所以我们这里析构的时候一定要与构造函数匹配,也就是用delete[]。
那我们这里如何才能保证我们这里的类型永远匹配呢?
我们就需要用到定制删除器。
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout<<"delete[]"<<ptr<<endl;
delete ptr[];
}
};
template<class T>
struct Free
{
void operator()(T* ptr)
{
cout<<"free"<<ptr<<endl;
free( ptr);
}
};
void test_shared_ptr3()
{
//使用仿函数
std::shared_ptr<Node> n1(new Node[5],DeleteArray<Node>());
std::shared_ptr<int> n2(new int[5],DeleteArray<int>());
std::shared_ptr<int> n3((int*)malloc(sizeof 12),Free<int>());
//使用lambda表达式
std::shared_ptr<Node> n4(new Node[5],[](Node*ptr){delete[] ptr;});
std::shared_ptr<int> n5(new int [5],[](int*ptr){delete[] ptr;});
std::shared_ptr<int> n6((int*)malloc(sizeof 12),[](int*ptr){free(ptr);});
std::shared_ptr<FILE> n7(fopen("test.txt","w"),[](FILE*ptr){ fclose(ptr);});
//只能用模板传入
std::unique_ptr<Node,DeleteArray<Node>> up(new Node[5]);
}
自己写一个定制删除器
#include <iostream>
#include <string>
using namespace std;
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr(){
cout<<"delete"<<endl;
delete _ptr;
}
T&operator*()
{
return *_ptr;
}
T&operator->()
{
return *_ptr;
}
private:
T* _ptr;
};
namespace zhuyuan
{
template<class T>
struct Delete
{
void operator()(T* ptr)
{
cout<<"delete:"<<ptr<<endl;
delete ptr;
}
};
template<class T,class D=Delete<T>>
class shared_ptr
{
public:
shared_ptr(T* ptr= nullptr)
:_ptr(ptr)
//创建一个类对象的时候,默认计数就是1
, _pCount(new int(1))
{}
void Release()
{
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
D()(_ptr);
delete _pCount;
}
}
~shared_ptr()
{
Release();
}
//拷贝构造
//sp1(sp2)
shared_ptr(const shared_ptr<T>& sp)
//将指针和计数都进行拷贝
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
//将计数++,表示又有一个对象对其进行了引用
(*_pCount)++;
}
// sp1 = sp3
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
//防止自己给自己赋值
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
private:
T* _ptr;
int* _pCount;
};
//辅助形智能指针,使命就是解决配合shared_ptr的循环引用问题。
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{
}
weak_ptr(const shared_ptr<T>&sp)
:_ptr(sp.get())
{
}
weak_ptr(weak_ptr<T>&sp)
:_ptr(sp._ptr)
{
}
weak_ptr<T> operator=(const shared_ptr<T>&sp)
{
_ptr=sp.get();
return * this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
public:
T* _ptr;
};
}
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout<<"delete[]"<<ptr<<endl;
delete[] ptr;
}
};
template<class T>
struct Free
{
void operator()(T* ptr)
{
cout<<"free"<<ptr<<endl;
free( ptr);
}
};
struct Node
{
int _val;
zhuyuan::weak_ptr<Node> _next;
zhuyuan::weak_ptr<Node> _prev;
~Node()
{
cout<<"~Node"<<endl;
}
};
测试代码
void test_shared_ptr4()
{
zhuyuan::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);
zhuyuan::shared_ptr<Node> n2(new Node);
zhuyuan::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
zhuyuan::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(12)));
}
int main()
{
test_shared_ptr4();
return 0;
}
内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
内存泄漏,在进程结束的时候,操作系统都是会回收的,我们为什么要解决内存泄漏?
因为我们很多服务器中的进程都是不会关掉的,内存泄漏会导致非常严重的后果
内幕才能泄漏是指针丢了还是内存丢了?
所有的内存泄漏都是指针丢了(失去了对内存的控制)。
内存还在,进程正常结束,内存也会释放,但是
1.僵尸进程有内存泄漏,内存并不会被被正确地释放。如果我们系统中的僵尸进程越来越多,那么我们的可用的内存就会变得越来越少,最终崩溃。
2.长期运行的程序
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
- 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。 - 采用RAII思想或者智能指针来管理资源。
- 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
- 出问题了使用内存泄漏工具检测。
(检测工具内部原理:申请内存用一个容器记录下来,释放内存时,从容器中删掉。程序结束前,或者没有任务跑时,容器中的资源可能就是内存泄漏的)
解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。