#includeusing 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和p2delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
RAII(Resource Acquisition Is Initialization)(资源请求即初始化,也就是获取到资源,马上就初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
①不需要显式地释放资源。
②采用这种方式,对象所需的资源在其生命期内始终保持有效
#includeusing namespace std;//利用RAII思想设计的delete资源的类
template
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout<<"delete"<int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{SmartPtr sp1(new int);SmartPtr sp2(new int);cout << div() << endl;cout<<"释放资源"<try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

不管是正方func结束,还是抛异常,sp1和sp2都会调用析构函数,释放资源。
但是我们这里就不能通过解引用的方式获取到资源了,这里我们就需要通过运算符重载来获取到我们的资源。
#includeusing namespace std;//利用RAII思想设计的delete资源的类
template
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout<<"delete"<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 sp1(new int);SmartPtr sp2(new int);cout << div() << endl;*sp1=1;*sp2=2;cout<<*sp1<try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
1.利用RAII思想设计delete类
2.重载*和->运算符
C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
#include
#include
using namespace std;
class A
{
public:~A(){cout<<"~A()"<auto_ptrap1(new A);return 0;
}

#include
#include
using namespace std;
class A
{
public:~A(){cout<<"~A()"<auto_ptrap1(new A);ap1->_a1++;ap1->_a2++;return 0;
}

因为智能指针在拷贝的时候,只有一个内置类型的拷贝,所以发生的是浅拷贝,会析构同一块空间,而二次析构内存就会报错,也就是我们的浅拷贝问题
#include
#include
using namespace std;template
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()"<SmartPtrsp1(new A);sp1->_a1++;sp1->_a2++;SmartPtrsp2(sp1);return 0;
}

解决方案:
1.深拷贝?不能,因为违背了功能需求。智能指针只是像指针一样,帮你托管空间。
但是像下面这种情况我们就需要的是浅拷贝。
list lt;
auto it=lt.begin();
为什么迭代器浅拷贝没有问题呢?
因为迭代器并不负责资源的管理!迭代器只是为了封装底层的细节,以统一的方式来遍历资源。它不管迭代器中资源的释放。
#include
#include
using namespace std;class A
{
public:~A(){cout<<"~A()"<auto_ptrsp1(new A);sp1->_a1++;sp1->_a2++;auto_ptrsp2(sp1);return 0;
}
库里面的是没有问题的。
因为库里面的行为就是将sp1的资源转移给了sp2。
是资源管理权转移,不负责任地拷贝,会导致被拷贝对象的悬空问题


手动实现一个auto_ptr,了解上述的C++98的智能指针的底层
#include
#include
using namespace std;
namespace zhuyuan
{templateclass auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){cout<<"delete"<&ap):_ptr(ap._ptr){ap._ptr=nullptr;}auto_ptr& operator=(auto_ptr& 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()"<zhuyuan::auto_ptr ap1(new A);ap1->_a1++;ap1->_a2++;cout<_a1<<" "<_a2< ap2(ap1);return 0;
}

这个时候如果我们再去调用sp1指针,我们的程序就会发生崩溃。
很多公司明确要求不能使用C++98的智能指针!
boost库
智能指针首先从boost社区中发展起来的。
scoped_ptr
shared_ptr
weak_ptr
C++11
unique_ptr
shared_ptr
weak_ptr
不允许拷贝,只要有拷贝就会报错
void test_unique_ptr()
{std::unique_ptr up2(new A);std::unique_ptr up1(up2);
}
手动实现
namespace zhuyuan
{templateclass unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}~ unique_ptr(){cout<<"delete"<&ap)=delete;unique_ptr& operator=( unique_ptr& ap)=delete;//防止拷贝C++98//只声明,不实现T&operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};}
void test_unique_ptr()
{zhuyuan::unique_ptr up2(new A);zhuyuan::unique_ptr up1(up2);up1->_a1++;up1->_a2++;zhuyuan::unique_ptrup3=up2;
}

unique_ptr:简单粗暴,不让拷贝,只适用于不需要拷贝的一些场景
那如果我们就是需要拷贝呢?
void test_shared_ptr()
{std::shared_ptr up2(new A);std::shared_ptr up1(up2);up1->_a1++;up1->_a2++;cout<_a1<<" "<_a2<up3=up2;
}

也就是说,它们共同管理了同一份数据。也就是三个指针指向的都是up2中的资源。
想要实现的话,我们就需要引用计数。
因为多个对象管理同一份资源,析构的时候就会出问题。但是,如果我们这时引入一个计数,也就是表示当前有多少个对象正在管理这份资源。当对象被析构的时候,我们就将这个计数–,当有新的对象引用这份资源的时候,我们就将这个计数++,当最后一个析构的对象释放时,释放这份资源。也就是说只需要析构一次就可以了。
这里使用静态计数对象是不可以滴。
因为一个资源就需要配一个计数,多个智能指针对象共管。
如果是静态对象的话,是所有资源都是有一个计数,因为静态成员属于整个类,类的所有对象。
每个资源需要管理时,会给构造函数,构造new一个计数。
namespace zhuyuan
{templateclass 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& sp)//将指针和计数都进行拷贝:_ptr(sp._ptr), _pCount(sp._pCount){//将计数++,表示又有一个对象对其进行了引用(*_pCount)++;}//赋值// sp1 = sp3shared_ptr& operator=(const shared_ptr& 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()"<
void test_shared_ptr()
{zhuyuan::shared_ptr up2(new A);zhuyuan::shared_ptr up1(up2);up1->_a1++;up1->_a2++;cout<_a1<<" "<_a2<up3=up2;zhuyuan::shared_ptrup4(new int);zhuyuan::shared_ptr sp5(new A);zhuyuan::shared_ptr sp6(sp5);}


重点注意
这里的赋值问题
// sp1 = sp3shared_ptr& operator=(const shared_ptr& 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;}}
如果两个智能指针指向同一块资源给多线程用的时候,里面可能会存在线程安全的风险。
struct Node
{int _val;//自定义类型的对象不能赋值给原生指针,//我们需要将其转换成智能指针std::shared_ptr _next;std::shared_ptr _prev;~Node(){cout<<"~Node"<//不支持隐式类型转换,所以我们需要使用()来进行赋值std::shared_ptr n1(new Node);std::shared_ptr 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!
weak_ptr不是常规智能指针,没有RAII,不支持直接管理资源
weak_ptr主要用shared_ptr构造,用来解决shared_ptr的循环引用的问题

可以使用shared_ptr进行构造,为了不增加引用计数,也就是不参与资源管理
struct Node
{int _val;std::weak_ptr _next;std::weak_ptr _prev;~Node(){cout<<"~Node"<//不支持隐式类型转换,所以我们需要使用()来进行赋值std::shared_ptr n1(new Node);std::shared_ptr 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 n1(new Node);std::shared_ptr n2(new Node);cout<_next=n2;n2->_prev=n1;cout<

内存泄漏,在进程结束的时候,操作系统都是会回收的,我们为什么要解决内存泄漏?
因为我们很多服务器中的进程都是不会关掉的!除非停服维修!并且有一些进程不能够正常释放