专业商城网站搭建价格,承德平台,创建网站站点,网站开发流程荆州一 、为什么需要智能指针
智能指针主要解决以下问题#xff1a; 1#xff09;内存泄漏#xff1a;内存手动释放#xff0c;使用智能指针可以自动释放
2#xff09;共享所有权指针的传播和释放#xff0c;比如多线程使用同一个对象时析构问题#xff0c;例如同样的数据…一 、为什么需要智能指针
智能指针主要解决以下问题 1内存泄漏内存手动释放使用智能指针可以自动释放
2共享所有权指针的传播和释放比如多线程使用同一个对象时析构问题例如同样的数据帧但是业务A和业务B处理的逻辑不一样都是只读。可以用shared_ptr共享数据帧对象的所有权。线程A释放的时候shared_ptr的引用计数count - 1当为0的时候释放数据帧对象指针。
主要类型 C里面的四个智能指针: auto_ptrshared_ptrunique_ptr weak_ptr 其中后三个是C11支持并且第一个已经被C11弃用。
几个指针的特点 1unique_ptr独占对象的所有权由于没有引用计数因此性能较好。 2shared_ptr共享对象的所有权但性能略差。 3weak_ptr配合shared_ptr解决循环引用的问题。
二、shared_ptr 共享的智能指针
2.1 内存模型
shared_ptr 内部包含两个指针一个指向对象另一个指向控制块(control block)控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据。 例如对于
std::shared_ptrint p1(new int(1));
std::shared_ptrint p2 p1;std::shared_ptr使用引用计数每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候内存才会被释放。
shared_ptr共享被管理对象同一时刻可以有多个shared_ptr拥有对象的所有权当最后一个shared_ptr对象销毁时被管理对象自动销毁。
2.2 常用函数
s.get() // 返回shared_ptr中保存的裸指针
s.reset(...) // 重置shared_ptr
s.use_count() // 返回shared_ptr的强引用计数
s.unique() // use_count()为1返回true否则返回false。reset( )不带参数时若智能指针s是唯一指向该对象的指针则释放并置空。若智能指针P不是唯一指向该对象的指针则引用计数减少1同时将P置空。
reset( )带参数时若智能指针s是唯一指向对象的指针则释放并指向新的对象。若P不是唯一的指针则只减少引用计数并指向新的对象。
2.3 初始化
通过构造函数、reset()、make_shared方法来初始化shared_ptr代码如下
std::shared_ptrint p1(new int(1));
std::shared_ptrint p2 p1;// 对于一个未初始化的智能指针可以通过reset方法来初始化
// 当智能指针有值的时候调用reset会引起引用计数减1
std::shared_ptrint p3;
p3.reset(new int(1));
if(p3) {
cout p3 is not null;
}我们应该优先使用make_shared来构造智能指针因为他更高效。
auto sp1 make_sharedint(100);
或
shared_ptrint sp1 make_sharedint(100);
//相当于
shared_ptrint sp1(new shared_ptrint sp1(new int(100)););这是因为为了节省一次内存分配原来 shared_ptrFoo x(new Foo); 需要为 Foo 和 ref_count 各分配一次内存现在用make_shared()的话可以一次分配一块足够大的内存供 Foo 和 ref_count 对象容身。
但是注意不能将一个原始指针直接赋值给一个智能指针例如下面这种方法是错误的
std::shared_ptrint p new int(1);正确的操作是通过构造函数和辅助方法来初始化
std::shared_ptrint p;
p1.reset(new int(1));例子
#include iostream
#include memory
using namespace std;int main()
{std::shared_ptrint p1;p1.reset(new int(1));std::shared_ptrint p2 p1;// 引用计数此时应该是2cout p2.use_count() p2.use_count() endl;p1.reset();cout p1.reset()\n;// 引用计数此时应该是2cout p2.use_count() p2.use_count() endl;if (!p1){cout p1 is empty\n;}if (!p2){cout p2 is empty\n;}p2.reset();cout p2.reset()\n;cout p2.use_count() p2.use_count() endl;if (!p2){cout p2 is empty\n;}return 0;
}结果是
p2.use_count() 2
p1.reset()
p2.use_count() 1
p1 is empty
p2.reset()
p2.use_count() 0
p2 is empty2.4 获取原始指针get
当需要获取原始指针时可以通过get方法来返回原始指针代码如下所示
std::shared_ptrint ptr(new int(1));
int *p ptr.get();谨慎使用p.get()的返回值因为很容易出错且难以排查。
p.get()的返回值就相当于一个裸指针的值不合适的使用这个值上述陷阱的所有错误都有可能发生遵守以下几个约定
1不要保存p.get()的返回值 无论是保存为裸指针还是shared_ptr都是错误的。 若保存为裸指针不知什么时候就会变成空悬指针若保存为shared_ptr则产生了独立指针
2不要delete p.get()的返回值 会导致对一块内存delete两次的错误。
2.5 指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时应当为其传递合适的删除器。
#include iostream
#include memory
using namespace std;
void DeleteIntPtr(int *p) {cout call DeleteIntPtr endl;delete p;
}
int main()
{std::shared_ptrint p(new int(1), DeleteIntPtr);return 0;
}当p的引用计数为0时自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达式上面的写法可以改为
std::shared_ptrint p(new int(1), [](int *p) {cout call lambda delete p endl;delete p;});特别的是当我们用shared_ptr管理动态数组时需要指定删除器因为shared_ptr的默认删除器不支持数组对象代码如下所示
std::shared_ptrint p(new int[10], [](int *p) { delete [] p;});2.6 使用shared_ptr要注意的问题
1、不要用一个原始指针初始化多个shared_ptr 例如下面错误范例
int *ptr new int;
shared_ptrint p1(ptr);
shared_ptrint p2(ptr); // 逻辑错误2、不要在函数实参中创建shared_ptr 对于下面的写法
function(shared_ptrint(new int), g()); //有缺陷因为C的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的一般是从右到左但也可能从左到右所以可能的过程是先new int然后调用g()如果恰好g()发生异常而shared_ptr还没有创建 则int内存泄漏了正确的写法应该是先创建智能指针代码如下
shared_ptrint p(new int);
function(p, g());3、通过shared_from_this()返回this指针 不要将this指针作为shared_ptr返回出来因为this指针本质上是一个裸指针因此这样可能会导致重复析构看下面的例子。
#include iostream
#include memory
using namespace std;
class A
{
public:shared_ptrA GetSelf(){return shared_ptrA(this); // 不要这么做}A(){coutConstruction Aendl;}~A(){cout Destructor A:this endl;}
};
int main()
{shared_ptrA sp1(new A);shared_ptrA sp2 sp1-GetSelf();coutsp1.use_count() sp1.use_count()endl;coutsp2.use_count() sp2.use_count()endl;return 0;
}运行后调用了两次析构函数。
Construction A
sp1.use_count() 1
sp2.use_count() 1
Destructor A:0x1024000
Destructor A:0x1024000在这个例子中由于用同一个指针this) 构造了两个智能指针sp1和sp2而他们之间是没有任何关系的即指向各自的 control block但是指向对象都是new int。因此 在离开作用域之后this将会被构造的两个智能指针各自析构导致重复析构的错误。
正确返回this的shared_ptr的做法是让目标类通过std::enable_shared_from_this类然后使用基类的成员函数shared_from_this()来返回this的shared_ptr如下所示。
#include iostream
#include memory
using namespace std;class A: public std::enable_shared_from_thisA
{
public:shared_ptrAGetSelf(){return shared_from_this(); //}A(){coutConstruction Aendl;}~A(){cout Destructor A endl;}
};
int main()
{shared_ptrA sp1(new A);shared_ptrA sp2 sp1-GetSelf(); // okcoutsp1.use_count() sp1.use_count()endl;coutsp2.use_count() sp2.use_count()endl;return 0;
}结果是
Construction A
sp1.use_count() 2
sp2.use_count() 2
Destructor A4、避免循环引用 循环引用会导致内存泄漏
#include iostream
#include memory
using namespace std;
class A;
class B;
class A {
public:std::shared_ptrB bptr;~A() {cout A is deleted endl;}
};
class B {
public:std::shared_ptrA aptr;~B() {cout B is deleted endl;}
};
int main()
{{std::shared_ptrA ap(new A);std::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导致两个指针都不会被析构产生内存泄漏。
解决的办法是把A和B任何一个成员变量改为weak_ptr具体方法见weak_ptr章节。
三、unique_ptr独占的智能指针
3.1 unique_ptr是一个独占型的智能指针
unique_ptr是一个独占型的智能指针它不允许其他的智能指针共享其内部的指针不允许通过赋值将 一个unique_ptr赋值给另一个unique_ptr。下面的错误示例。
unique_ptrT my_ptr(new T);
unique_ptrT my_other_ptr my_ptr; // 报错不能复制unique_ptr不允许复制但可以通过函数返回给其他的unique_ptr还可以通过·std::move·来转移到其他的unique_ptr这样它本身就不再拥有原来指针的所有权了。例如
unique_ptrT my_ptr(new T); // 正确
unique_ptrT my_other_ptr std::move(my_ptr); // 正确
unique_ptrT ptr my_ptr; // 报错不能复制std::make_shared是c11的一部分但std::make_unique不是。它是在c14里加入标准库的。
auto upw1(std::make_uniqueWidget()); // with make func
std::unique_ptrWidget upw2(new Widget); // without make func3.2 unique_ptr可以指向一个数组
std::unique_ptrint [] ptr(new int[10]);
ptr[9] 9;
std::shared_ptrint [] ptr2(new int[10]); // 这个是不合法的3.3 unique_ptr需要确定删除器的类型
unique_ptr需要确定删除器的类型所以不能像shared_ptr那样直接指定删除器
std::shared_ptrint ptr3(new int(1), [](int *p){delete p;}); // 正确
std::unique_ptrint ptr4(new int(1), [](int *p){delete p;}); // 错误关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。
如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr如果希望多个智能指针管理同一个资源就用shared_ptr。
四、weak_ptr弱引用的智能指针
shared_ptr虽然已经很好用了但是有一点shared_ptr智能指针还是有内存泄露的情况当两个对象相互 使用一个shared_ptr成员变量指向对方会造成循环引用使引用计数失效从而导致内存泄漏。
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr weak_ptr 只是提供了对管理对象的一个访问手段。
weak_ptr 设计的目的是为配合 shared_ptr而引入的一种智能指针来协助 shared_ptr工作, 它只可以从一个 shared_ptr或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
4.1 基本用法
1通过use_count()方法获取当前观察资源的引用计数如下所示
shared_ptrint sp(new int(10));
weak_ptrint wp(sp);
cout wp.use_count() endl; //结果讲输出12通过expired()方法判断所观察资源是否已经释放如下所示
shared_ptrint sp(new int(10));
weak_ptrint wp(sp);
if(wp.expired())cout weak_ptr无效,资源已释放;
elsecout weak_ptr有效;3通过lock方法获取监视的shared_ptr
#include iostream
#include memory
using namespace std;std::weak_ptrint gw;
void f()
{auto spt gw.lock(); if(gw.expired()) {cout gw无效,资源已释放;}else {cout gw有效, *spt *spt endl;}
}
int main()
{{auto sp std::make_sharedint(42);gw sp;f();}f();
return 0;
}结果是
gw有效, *spt 42
gw无效,资源已释放4.2 返回this指针
shared_ptr章节中提到不能直接将this指针返回shared_ptr需要通过派生std::enable_shared_from_this类并通过其方法shared_from_this来返回指针. 原因是std::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 std::enable_shared_from_thisA
{
public:shared_ptrAGetSelf(){return shared_from_this(); //}A(){coutConstruction Aendl;}~A(){cout Destructor A endl;}
};
int main()
{shared_ptrA sp1(new A);shared_ptrA sp2 sp1-GetSelf(); // okcoutsp1.use_count() sp1.use_count()endl;coutsp2.use_count() sp2.use_count()endl;return 0;
}结果是
Construction A
sp1.use_count() 2
sp2.use_count() 2
Destructor A在外面创建A对象的智能指针和通过对象返回this的智能指针都是安全的因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针在离开作用域之后sp1的引用计数减为0A对象会被析构不会出现A对象被析构两次的问题。
需要注意的是获取自身智能指针的函数尽在shared_ptr的构造函数被调用之后才能使用因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。
需要注意的是获取自身智能指针的函数尽在shared_ptr的构造函数被调用之后才能使用因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。
4.3 解决循环引用问题
在shared_ptr章节提到智能指针循环引用的问题因为智能指针的循环引用会导致内存泄漏可以通过weak_ptr解决该问题只要将A或B的任意一个成员变量改为weak_ptr
#include iostream
#include memory
using namespace std;
class A;
class B;
class A {
public:std::weak_ptrB bptr;~A() {cout A is deleted endl;}
};
class B {
public:std::shared_ptrA aptr;~B() {cout B is deleted endl;}
};
int main()
{{std::shared_ptrA ap(new A);cout ap.use_count(): ap.use_count()endl;std::shared_ptrB bp(new B);cout bp.use_count():bp.use_count()endl;coutendl;ap-bptr bp;cout ap.use_count():ap.use_count()endl;cout bp.use_count():bp.use_count()endl;coutendl;bp-aptr ap;cout ap.use_count():ap.use_count()endl;cout bp.use_count():bp.use_count()endl;}cout main leave endl; // 循环引用导致ap bp退出了作用域都没有析构return 0;
}ap.use_count():1
bp.use_count():1ap.use_count():1
bp.use_count():1ap.use_count():2
bp.use_count():1
B is deleted
A is deleted
main leave这样在对B的成员赋值时即执行ap-bptr bp;;时由于bptr是weak_ptr它并不会增加引用计数所以bp的引用计数仍然会是1。 在离开作用域之后bp的引用计数为减为0B指针会被析构析构后其内部的aptr的引用计数会被减为1然后在离开作用域后ap引用计数又从1减为0A对象也被析构不会发生内存泄漏.
4.4 使用注意事项
1、weak_ptr在使用前需要检查合法性。
weak_ptrint wp;
{shared_ptrint sp(new int(1)); //sp.use_count()1wp sp; //wp不会改变引用计数所以sp.use_count()1shared_ptrint sp_ok wp.lock(); //wp没有重载-操作符。只能这样取所指向的对象
}
shared_ptrint sp_null wp.lock(); //sp_null .use_count()0;因为上述代码中sp和sp_ok离开了作用域其容纳的对象已经被释放了得到了一个容纳NULL指针的sp_null对象。
在使用wp前需要调用wp.expired()函数判断一下。因为wp还仍旧存在虽然引用计数等于0仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构这块“堆”存储块才能被回收。否则weak_ptr无法指导自己所容纳的那个指针资源的当前状态。
weak_ptrint wp;
shared_ptrint sp_ok;
{shared_ptrint sp(new int(1)); //sp.use_count()1wp sp; //wp不会改变引用计数所以sp.use_count()1sp_ok wp.lock(); //wp没有重载-操作符。只能这样取所指向的对象
}
// 在这个作用域结束后sp 和其所管理的内存将会被销毁因为 sp 的引用计数变为 0触发内存释放。
if(wp.expired()) {cout shared_ptr is destroy endl;
} else {cout shared_ptr no destroy endl;
}shared_ptr no destroy在作用域内创建了一个新的shared_ptr对象sp并将其所有权转交给wp然后通过调用wp.lock()方法从wp中获取了shared_ptr的所有权。由于wp仍然有效所以输出结果为shared_ptr no destroy。当作用域结束时sp所管理的int对象会被销毁但这并不影响wp的有效性。
五、为什么多线程读写 shared_ptr 要加锁
为什么多线程读写 shared_ptr 要加锁
本专栏知识点是通过零声教育的系统学习进行梳理总结写下文章对c/clinux课程感兴趣的读者可以点击链接,详细查看详细的服务器课程