宁波自助建站模板,金华企业自助建站系统,运城门户网站建设,中山网站设计1. 为什么要使用智能指针#xff1f;
智能指针可以解决忘记释放内存导致内存泄漏的问题#xff1b;智能指针可以解决异常安全问题。
2. 智能指针的原理
RAII#xff08;Resource Acquisition Is Initialization#xff09;是一种利用对象生命周期来控制程序资源#xf…1. 为什么要使用智能指针
智能指针可以解决忘记释放内存导致内存泄漏的问题智能指针可以解决异常安全问题。
2. 智能指针的原理
RAIIResource Acquisition Is Initialization是一种利用对象生命周期来控制程序资源如内存、文件句柄、网络连接、互斥量等等的简单技术。在对象构造时获取资源接着控制对资源的访问使之在对象的生命周期内始终保持有效最后在对象析构的时候释放资源。借此我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处 不需要显示地释放资源。采用这种方式对象所需的资源在其生命期内始终保持有效。
例子1不使用智能指针
#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(){int *p1 new int;cout div() endl;delete p1;
}int main(){try{Func();}catch(exception e){cout e.what() endl;}return 0;
}new空间也有可能会抛出异常对于 p1 如果抛出异常没有问题可以不管直接到最外面去了。如果用户输入的除数为0那么div函数就会抛出异常跳到主函数的catch块中执行此时Func()中的申请的内存资源还没有释放就会发生内存泄漏。
例子2在例子1基础上对new进行异常捕获
void Func(){int* p1 new int;try{cout div() endl;}catch (...){delete p1;throw;}delete p1;
}如果还要申请的p2,p3…这时候就需要套很多。因此要根本解决这个问题可以使用智能指针。
例子3使用智能指针
#include iostream
using namespace std;templateclass T
class smart_ptr{
public:smart_ptr(T *ptr nullptr):_ptr(ptr){ }~smart_ptr(){if(_ptr){cout delete: _ptr endl;delete _ptr;}}T operator*(){return *_ptr;}T* operator-(){return _ptr;}T operator[](size_t pos){return _ptr[pos];}
private:T *_ptr;
};int div(){int a, b;cin a b;if(b 0){throw invalid_argument(除0错误);}return a / b;
}void Func(){smart_ptrint sp1(new int);int *p2 new int;smart_ptrint sp2(p2);cout div() endl;
}int main(){try{Func();}catch(exception e){cout e.what() endl;}return 0;
}在构造smart_ptr对象时自动调用构造函数将传入的需要管理的内存保存起来;在析构smart_ptr对象时自动调用析构函数将管理的内存空间进行释放smart_ptr还可以与普通指针一样使用需对*和-以及[]进行运算符重载问题 如果用一个smart_ptr对象来拷贝构造另一个smart_ptr对象或者一个smart_ptr对象赋值给另一个smart_ptr对象最终结果会导致程序崩溃原因编译器默认生成的拷贝构造函数对内置类型完成浅拷贝(值拷贝)单纯的浅拷贝会导致空间多次释放。
3. C11中的几种智能指针
3.1 shared_ptr
通过引用计数的方式解决智能指针的拷贝问题shared_ptr 内部包含两个指针一个指向对象另一个指向控制块(control block)控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据shared_ptr使用引用计数每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候内存才会被释放。shared_ptr共享被管理对象同一时刻可以有多个shared_ptr拥有对象的所有权当最后一个shared_ptr对象销毁时被管理对象自动销毁。
int main(){shared_ptrint sp1(new int(1));shared_ptrint sp2(sp1);*sp1 10;*sp2 20;cout sp1.use_count() endl; // 2shared_ptrint sp3(new int(1));shared_ptrint sp4(new int(2));sp3 sp4;cout sp3.use_count() endl; // 2return 0;
}shared_ptr常用函数和基本方法
初始化和reset sp.reset()重置shared_ptrreset()不带参数时若智能指针sp是唯一指向该对象的指针则释放并置空。若智能指针sp不是唯一指向该对象的指针则引用计数减少1同时将sp置空sp.reset(new int(200))reset()带参数时若智能指针sp是唯一指向对象的指针则释放并指向新的对象。若sp不是唯一的指针则只减少引用计数并指向新的对象
shared_ptrint p1(new int(1));
shared_ptrint p2 p1;
shared_ptrint p3;
p3.reset(new int(1));使用make_shared来构造智能指针更高效
auto sp1 make_sharedint(100);
或
shared_ptrint sp1 make_sharedint(100);// shared_ptrint p new int(1); // 这是错误的不能通过直接用原始赋值来初始化获取原始指针
shared_ptrint ptr(new int(1));
int *p ptr.get(); // 返回shared_ptr中保存的裸指针指定删除器 如果用shared_ptr管理非new对象或是没有析构函数的类时应当为其传递合适的删除器。当p的引用计数为0时自动调用删除器DeleteIntPtr来释放对象的内存。
#include iostream
#include memory
using namespace std;void DeleteIntPtr(int *p){cout call DeleteIntPtr endl;delete p;
}int main(){shared_ptrint p(new int(1), DeleteIntPtr);return 0;
}或者使用lambda表达式
shared_ptrint p(new int(1), [](int*p)){cout call DeleteIntPtr endl;delete p; });当我们用shared_ptr管理动态数组时需要指定删除器因为shared_ptr的默认删除器不支持数组对象
shared_ptrint p3(new int[10], [](int *p) { delete [] p; });shared_ptr线程安全问题
管理同一个资源的多个对象共享引用计数多个线程可能会同时对同一个个引用计数进行加或减而自增或自减都不是原子操作所以需要通过加锁对引用计数进行保护。通过加锁让引用计数的、-- 操作变成原子操作对引用计数的操作进行加锁保护也可以用原子类atomic对引用计数封装。
templateclass T
class shared_ptr{
public:shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1)), _pmtx(new mutex){}~shared_ptr(){Release();}shared_ptr(const shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount),_pmtx(sp._pmtx){_pmtx-lock();(*_pcount);_pmtx-unlock();}// flag作用:当引用计数减到0时需要释放互斥锁但是不能在临界区直接进行释放因为后面还要解锁// 所以可以通过flag去标记判断解锁后是否释放互斥锁资源void Release(){bool flag false;_pmtx-lock();if (--(*_pcount) 0){delete _pcount;delete _ptr;flag true;}_pmtx-unlock();if (flag true) delete _pmtx;}shared_ptrT operator(const shared_ptrT sp){if (_ptr ! sp._ptr){Release();_pcount sp._pcount;_ptr sp._ptr;_pmtx sp._pmtx;_pmtx-lock();(*_pcount);_pmtx-unlock();}return *this;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}T operator[](size_t pos){return _ptr[pos];}int use_count(){return *_pcount;}
private:T* _ptr;int* _pcount;mutex* _pmtx;
};shared_ptr本身是线程安全的拷贝和析构时引用计数、-- 都是线程安全的不需要保证管理的资源的线程安全问题而shared_ptr管理资源的访问不是线程安全的需要用的地方自行保护。
struct Date{int _year 0;int _month 0;int _day 0;
};
void test_shared_ptr(){int n 100000;mutex mtx;shared_ptrDate sp1(new Date);thread t1([](){for (int i 0; i n; i){shared_ptrDate sp2(sp1);mtx.lock();sp2-_year;sp2-_month;sp2-_day;mtx.unlock();}});thread t2([](){for (int i 0; i n; i){shared_ptrDate sp3(sp1);mtx.lock();sp3-_year;sp3-_month;sp3-_day;mtx.unlock();}});t1.join();t2.join();cout sp1.use_count() endl;cout sp1-_year endl;cout sp1-_month endl;cout sp1-_day endl;
}注意事项 不要用一个原始指针初始化多个shared_ptr 不要在函数实参中创建shared_ptr
function(shared_ptrint(new int), g()); //有缺陷// 正确做法应该是先创建智能指针
shared_ptrint p(new int);
function(p, g());不要将this指针作为shared_ptr返回出来因为this指针本质上是一个裸指针这样可能会导致重复析构
class A{
public:shared_ptrA GetSelf(){ return shared_ptrA(this); }~A(){ cout Destructor A endl; }
};
int main(){shared_ptrA sp1(new A);shared_ptrA sp2 sp1-GetSelf();return 0;
}由于用同一个指针this)构造了两个智能指针sp1和sp2而他们之间是没有任何关系的在离开作用域之后this将会被构造的两个智能指针各自析构导致重复析构的错误。正确返回this的shared_ptr的做法是让目标类通过std::enable_shared_from_this类然后使用基类的成员函数shared_from_this()来返回this的shared_ptr
class A: public enable_shared_from_thisA{
public:shared_ptrA GetSelf(){ return shared_from_this(); }~A(){ cout Destructor A endl; }
};
int main(){shared_ptrA sp1(new A);shared_ptrA sp2 sp1-GetSelf();return 0;
}避免循环引用循环引用会导致内存泄漏
class A;
class B;class A{
public:shared_ptrB bptr;~A(){ cout A is deleted endl; }
};class B{
public:shared_ptrA aptr;~B(){ cout B is deleted endl; }
};int main(){{shared_ptrA ap(new A);shared_ptrB bp(new B);ap-bptr bp;bp-aptr ap;}cout main leave endl; // 循环引用导致ap bp退出了作用域都没有析构return 0;
}循环引用导致ap和bp的引用计数为2在离开作用域之后ap和bp的引用计数减为1并不回减为0导致两个指针都不会被析构产生内存泄漏。
3.2 weak_ptr
share_ptr智能指针还是有内存泄露的情况当两个对象相互使用一个shared_ptr成员变量指向对方会造成循环引用使引用计数失效从而导致内存泄漏。weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的shared_ptrweak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
weak_ptr 使用方法
shared_ptrint p1(new int(10));
weak_ptrint p2(p1);// 通过use_count()方法获取当前观察资源的引用计数
cout p2.use_count() endl; // count 1// 通过expired()方法判断所观察资源是否已经释放
if(p2.exxpired()) cout weak_ptr无效,资源已释放 endl;
else cout weak_ptr有效 endl;// 通过lock方法获取监视的shared_ptr
weak_ptrint wp;
void func(){auto spt wp.lock();if(wp.expired()) cout weak_ptr无效,资源已释放 endl;else cout weak_ptr有效,*spt *spt endl;
}
int main(){{auto sp make_sharedint(42);wp sp;func();}func();return 0;
}weak_ptr返回this指针
shared_ptr中提到不能直接将this指针返回shared_ptr需要通过派生enable_shared_from_this类并通过其方法shared_from_this来返回指针 原因是enable_shared_from_this类中有一个weak_ptr这个weak_ptr用来观察this智能指针调用shared_from_this()方法是会调用内部这个weak_ptr的lock()方法将所观察的shared_ptr返回
#include iostream
#include memory
using namespace std;class A: public enable_shared_from_thisA{
public:share_ptrAGetSelf(){ return shared_from_this(); }~A(){ cout Destructor A endl; }
};
int main(){shared_ptrA sp1(new A);shared_ptrA sp2 sp1-GetSelf();return 0;
}在外面创建A对象的智能指针和通过对象返回this的智能指针都是安全的因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针在离开作用域之后spy的引用计数减为0A对象会被析构不会出现A对象被析构两次的问题。获取自身智能指针的函数尽在shared_ptr的构造函数被调用之后才能使用因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。
weak_ptr 解决循环引用问题
只要将A或B的任意一个成员变量改为weak_ptr即可解决智能指针的循环引用导致内存泄漏的问题
class A;
class B;class A {
public:weak_ptrB bptr;~A(){ cout A is deleted endl; }
};class B{
public:shared_ptrA aptr;~B(){ cout B is deleted endl; }
};int main(){{shared_ptrA ap(new A);shared_ptrB bp(new B);ap-bptr bp;bp-aptr ap;}return 0;
}3.3 unique_ptr
unique_ptr独占对象的所有权没有引用计数。同⼀时刻只能有⼀个unique_ptr指向给定对象离开作⽤域时若其指向对象则将其所指对象销毁默认delete它不允许其他的智能指针共享其内部的指针不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr定义unique_ptr时需要将其绑定到⼀个new返回的指针上unique_ptr不⽀持普通的拷⻉和赋值因为拥有指向的对象但是可以拷⻉和赋值⼀个将要被销毁的unique_ptr可以通过release或者reset将指针所有权从⼀个⾮const unique_ptr转移到另⼀个unique
unique_ptrT my_ptr(new T);
unique_ptrT my_other_ptr move(my_ptr); // 正确
unique_ptrT my_other_ptr my_ptr; // 报错不能复制unique_ptr可以指向一个数组
unique_ptrint [] ptr(new int[10]);
ptr[9] 9;
shared_ptrint [] ptr2(new int[10]); // 这个是不合法的unique_ptr指定删除器和shared_ptr有区别
shared_ptrint ptr1(new int(1), [](int *p){delete p;}); // 正确
unique_ptrint ptr2(new int(1), [](int *p){delete p;}); // 错误
unique_ptrint, void(*)(int*) ptr3(new int(1), [](int *p){delete p;}); // 正确如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr如果希望多个智能指针管理同一个资源就用shared_ptr。