C++ 11 智能指针
C++ 11 智能指针
前言:
近来,学习STL,突然发现有智能指针,做了一周的学习(工作之外的时间),断断续续的学习,特此做下记录。
诞生的原因:
为了防止内存泄露,和二次释放的问题。无非就是嫌弃自己管理内存太费劲,可以写个更简单管理堆内存的类。
利用C++的特性:
类结束会调用析构函数,无非就是栈空间出栈,同时释放掉动态创建的空间。
智能指针的作用:
将指针封装成类,利用了一种叫RAII(资源获取即初始化的技术,听着有点高大上),重载操作符(->和*),行为表现的像指针
- 防止多次释放该指针,导致崩溃(前提是这个指针被你释放是否赋值为空指针,如果赋值为空,就没有所谓的崩溃问题,习惯决定代码的健壮性)
- 智能指针作用把值语义转为引用(总是搞个二传手,这就是所谓的安全,降低性能为代价,在内存无比大的今天,随意了)
C++ 11 中的智能指针:
包含在头文件<memory>中,分别有三个智能指针,分别为shared_ptr,unique_ptr,weak_ptr。
- 介绍下shared_ptr(别人写的很好,我只做网络的搬运工,来丰富自己的知识体系)
1)原理:shared_ptr的多个对象指向同一个指针(大多是new出来的空间指针),该指针使用引用计数,每使用一次,内部计数器加1,每析构一次,内部的引用计数器减1,减为0的时候,自动删除指向的堆内存。
2)实现:就是一个模板类,没事的时候强烈建议看下里面的具体实现,挺有意思的。
3)不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
4)注意避免循环使用,会在下面举例,并讲解。
2.unique_ptr
“唯一”拥有所指对象,同一时刻只能有一个unique_ptr指向给定对象(禁止使用拷贝语义,只能用移动语义将其移动)。对比原始指针,也是利用了RAII的特性。用户可以定义delete操作。
#include <iostream> #include <memory> using namespace std; int main() { unique_ptr<int> uptr(new int(10)); //初始化 unique_ptr<int> uptr1 = move(uptr); //转移所有权 uptr2.release(); //释放所有权 return 0; }
3.weak_ptr,配合shared_ptr而引入的的智能指针,是弱引用,相对于shared_ptr强引用来说的。看似就像一个观察者,观测资源的使用情况。weak_ptr可以从一个shared_ptr获取另一个weak_ptr来构造,获取资源的观察权 。 并不会引起计数加的情况,成员有use_count(),查看资源的引用计数,expired(),判断是否指向的资源被释放, 当返回为true的时候,这个资源的引用计数为0,相当于被释放,反之就没有被释放掉。lock(),返回当前分享指 针,计数器并加1.
#include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> s_ptr = make_shared<int>(10); cout<<s_ptr.use_count() <<endl; weak_ptr<int> wp(s_ptr); cout<<wp.use_count() <<endl; if(!wp.expired()) { shared_ptr s_ptr2 = wp.lock(); //引用计数器加1 *s_ptr = 100; cout<<wp.use_count()<<endl; } } 运行结果: 1 1 2
应用场景及其问题:
1.循环引用,场景:考虑一个简单的场景--家长和孩子,一个父母有一个孩子,一个孩子有一双父母。
使用原始指针的实现:
#include <iostream> using namespace std; class Child; class Parent; class Parent { private: Child* myChild; public: void setChild(Child* ch) { this->myChild = ch; } void doSomething() { if(this->myChild) { cout<<"Child alive"<<endl; } } ~Parent() { cout<<"delete myChild"<<endl; delete myChild; } }; class Child { private: Parent* myParent; public: void setParent(Parent* p) { this->myParent = p; } void doSomething() { if(this->myParent) { cout<<"myParent alive"<<endl; } } ~Child() { cout<<"delete myParent"<<endl; delete myParent; } }; int main() { Parent* p = new Parent; Child* c = new Child; p->setChild(c); c->setParent(p); delete c; return 0; }
如何使用智能指针解决该问题呢:引入智能指针,两个类只要保证一个类是shared_ptr(强引用)一个是weak_ptr(弱引用)
#include <memory> #include <iostream>
class Child; class Parent; class Parent{ private: std::weak_ptr<Child> ChildPtr; public: void setChild(std::shared_ptr<Child> child) { this->ChildPtr = child; } void doSomething() { } ~Parent() {} }; class Child { private: std::shared_ptr<Parent> ParentPtr; public: void setParent(std::shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() {} ~Child() {} }; int main() { std::weak_ptr<Parent> wpp; std::weak_ptr<Child> wpc; { std::shared_ptr<Parent> p(new Parent); std::shared_ptr<Child> c(new Child); p->setChild(c); c->setParent(p); wpp = p; wpc = c; std::cout<<p.use_count() <<std::endl; std::cout<<c.use_count() <<std::endl; } std::cout <<wpp.use_count() << std::endl; std::cout << wpc.use_count() << std::endl; return 0; }
运行结果:
2
1
0
0
注意如果使用g++编译,请添加参数-std=c++11(弱引用是C++11引入)
2..返回shared_ptr本身,并不引起计数器加+1
#include<iostream> #include<memory> using namespace std; class Test: public enable_shared_from_this<Test> { public: Test(){} ~Test() {cout <<"~Test()"<<endl;} shared_ptr<Test> sget() { return shared_from_this(); } }; int main() { weak_ptr<Test> wp; { shared_ptr<Test> sp(new Test); wp = sp; cout<<"sp is "<<sp.use_count()<<endl; sp->sget(); cout<<"sp is "<<sp.use_count()<<endl; } cout<<"wp is"<<wp.use_count()<<endl; return 0; return 0; } 运行结果: sp is 1 sp is 1 ~Test() wp is 0
3.注册销毁函数
#include<iostream> #include<memory> using namespace std; struct MyStruct { int *p; MyStruct():p(new int(10)){} }; int main() { MyStruct st; { shared_ptr<MyStruct> sp(&st, [](MyStruct *ptr){ delete(ptr->p); ptr->p = nullptr; cout<<"destructed"<<endl; }); } if(st.p != nullptr) cout<<"no destroyed"<<endl; else cout<<"be destroyed"<<endl; return 0; } 运行结果: destructed be destroyed
4.线程安全讨论
官方文档:1)同一个shared_ptr对象可以被多线程同时读取
2) 不同的shared_ptr对象可以被多线程同时修改(提起12分注意力,这里有坑)
3)任何其他并发访问的结果都是无定义的(什么软,这个目前无法理解)
对1)来说,都能理解,读一定是安全,当在2)情况下,由于内部shared_ptr有两个成员,一个计数,一个指向
实际内存的指针,具体内部实现也没有上锁,同时操作两个数据成员,读写操作无法做到原子化, 在多线程编程 中,在多个线程同时访问同一个shared_ptr的时 候,请加mutex保护。
下面来详细的分析下为什么:
1)首先看下shared_ptr内存结构,加入该指针指向一个Foo的类
2)考虑一个简单的场景,有三个shared_ptr<Foo> 对象 x, g,n;
shared_ptr<Foo> g(new Foo); //线程之间共享的shared_ptr
shared_ptr<Foo> x; //线程A的局部变量
shared_ptr<Foo> n(new Foo);//线程B的局部变量
开始:还挺整齐的,符合我们的预想
线程A执行x = g;即(read g),以下图示:但是还没来的急将引用计数+1,切换到线程B
同时线程B执行g=n;(即写g),如下图
这个时候就已经将Foo1申请的动态内存归还给操作系统了,出现空悬指针,如下图:
最后将回到线程A,如下图:
现在这个状态,整个人都不好了。
多线程无保护的读写,造成了"x空悬指针"的后果,综上,论证为啥对shared_ptr读写要加锁的原因。
以上就是对智能指针的理解
具体参考:http://www.cnblogs.com/gqtcgq/p/7492772.html
vs2010中关于C++11 智能指针的源码
23:37:57 2019-04-26