share_ptr解析
- 共享所有权
- 存储成本较裸指针多了引用计数指针(和想关控制块-共享)
- 接口慎用(蔓延问题)
- 线程安全,引用结束增减会减慢多核性能
- 最适合共享的不变数据
- 支持拷贝构造,支持移动
share_ptr内存模型图
创建share_ptr易犯错的代码
Widget* p=new Widget();
shared_ptr<Widget> spw(p);
shared_ptr<Widget> spw2(p);
// share_ptr都指向了p ,但是引用计数块不是共享的,各自的,spw2为1 , spw也为1 spw2释放了内存后,spw还会继续释放相同内存P
//应该写成
shared_ptr<Widget> spw2(spw)
关于继承场景下,使用share_ptr的代码注意点
auto b1=std::make_shared<Base>();//父类
b1->process();
auto s1=std::make_shared<Sub>();//子类
s1->process();
share_Ptr<Base> b2{b1};//可以, 发生拷贝构造
share_Ptr<Base> s2{s1};//可以, 发生拷贝构造
share_Ptr<Base> b2=std::static_point_cast<Base>(s1);//OK 子类智能指针初始化父类智能指针推荐做法
share_ptr<Base> s22{s1}; //ok 但更推荐写法是这样 shared_ptr<Base> s22=std::static_point_cast<Base>(s1);
//取出b2指向的真实指针,再做dynamic_cast<Sub*>转型,再基于这个创建share_Ptr,
//程序编译没问题,运行会挂掉, s3 和 b1指向同一块内存,但引用计数是各自的,造成同时释放同一块内存,这个是平时可能写的错误代码
share_ptr<Sub> s3=share_Ptr<Sub>(dynamic_cast<Sub*>b2.get());//错误写法,要小心!!!
//注意:智能指针在.get()方法的时候要非常小心,你最多只能使用裸指针,不要去delete这个裸指针,也不要用这个裸指针封装到另一个之智能指针里面
//怎么安全的将父类的share_ptr转为子类的share_ptr??
shared_ptr<Sub> s22=std::dynamic_point_cast<Sub>(b2);//ok ,共享内存,引用计数共享
unique_ptr 和 share_ptr 存在转换关系,unique_ptr 可以转成share_ptr, 反之不可以
std::unique_ptr<std::string> foo(){
return std::make_unique<std::string>("testFoo");
}
//相当于 unique_ptr 交出裸指针,释放所有权,share_ptr 基于裸指针创建
std::share_ptr<std::string> sp1=foo();
std::unique_ptr<std::string> up1=std::make_unique<std::string>("test");
std::share_ptr<std::string> sp1=std::move(up1);
关于share_Ptr 与weak_ptr 的话题
问题代码1
class A
{
int dataA_;
std::share<B> ptrb_;
};
class B
{
int dataB_
std::share<A> ptra_;
};
int main{
std::share_ptr<A> ptra=std::make_share<A>();
std::share_ptr<B> ptrb=std::make_share<B>();
ptra->ptrb_ =ptrb;
ptrb->ptra_ =ptra;
}
//出现问题? 及问题解析 如下
如上图所示,main函数栈结束的时候,
第一步 ptrb 先销毁,从上图中可以看到除了自己指向了对象B, 堆上还有一个指针指向了这个B
所以这个对象B 引用计数是2,当ptrb退出main函数栈 ,计数减1,计数还剩1,所以B对象没有被销毁
第二步:ptra销毁,ptra销毁的时候发现除了自己指向对象A,还有个堆对象指向了对象A,
所以这个对象A 引用计数也是2,当ptra退出main函数栈 ,计数减1,计数还剩1,A对象没有被销毁
所以照成两个堆上的对象都没有释放掉,这个就是循环引用问题
问题代码2如下
class A
{
int dataA_;
std::share<B> ptrb_;
};
class B
{
int dataB_
std::share<C> ptrc_;
};
class C
{
int dataB_
std::share<A> ptra_;
};
int main{
std::share_ptr<A> ptra=std::make_share<A>();
std::share_ptr<B> ptrb=std::make_share<B>();
std::share_ptr<C> ptrc=std::make_share<C>();
ptra->ptrb_ =ptrb;
ptrb->ptrc_ =ptrc;
ptrc->ptra_ =ptra;
}
内存结构如图像, 堆上的A,B,C 都无法释放
下面我们看看如何通过弱引用上面循环引用内存泄漏的解决
#include "iostream"
#include "memory"
class B;
class A{
public:
int data;
A(){
std::cout<<"A()对象构造"<<std::endl;
}
~A(){
std::cout<<"~A()析构"<<std::endl;
}
std::shared_ptr<B> ptrb_;
private:
};
class B{
public:
int data;
B(){
std::cout<<"B()对象构造"<<std::endl;
}
~B(){
std::cout<<"~B()析构"<<std::endl;
}
std::weak_ptr<A> ptra_; //弱引用
};
void play(){
std::shared_ptr<A> ptra=std::make_shared<A>();
std::shared_ptr<B> ptrb=std::make_shared<B>();
ptra->ptrb_=ptrb;
ptrb->ptra_=ptra;
}
int main(){
play();
return 1;
}
//执行结果
A()对象构造
B()对象构造
~A()析构
~B()析构
看内存结构图如下
过程解析
play()函数栈销毁过程
第1步: std::shared_ptr<B> ptrb 栈对象先销毁, 栈对象销毁的时候,share_ptr析构函数中发现引用计数为2,所以
就只能完成 引用计数减一操作, 堆对象B没能得到释放
第2步: std::shared_ptr<A> ptra 栈对象准备销毁,栈对象销毁的时候,因为
B指向A的是个弱引用,share_ptr析构函数中发现引用计数为1,所以堆上A对象准备先析构销毁,
而A对象中还有个成员 std::shared_ptr<B> ptrb_;,这个对象也要准备销毁,调用shared_ptr的析构函数,这个时候
对象B的计数是1 了,所以这个时候 ~B()析构
关于enalbe_shared_from_this<>问题
问题代码1
class Widget{
int m_x;
int m_y;
int m_z;
public:
Widget(int x,int y , int z): m_x(x), m_y(y),m_z(z)
{}
void print(){
cout<<m_x<<","<<m_y<<","<<m_z<<endl;
}
unique_ptr<Widget> getWidget()
{
return unique_ptr<Widget>(this);
}
~Widget()
{
cout<<"Widget dtor"<<endl;
}
};
Widget* p=new Widget(1,2,3);
unique_ptr<Widget> w2{p};
unique_ptr<Widget> w3=p->getWidget(); //--》等价 getWidget(p)
//上面代码问题
//使用同一个堆对象内存创建了多个 unique_ptr,导致会多次销毁同一个对象
//所以不要 unique_ptr<Widget>(this); 这么做!!!!!!!!!!
问题代码2
class Widget {
public:
Widget()
{
cout<<"Widget()"<<endl;
}
shared_ptr<Widget> getWidget() {
return shared_ptr<Widget>(this);
}
// shared_ptr<Widget> getWidget(Widget* this) {
// return shared_ptr<Widget>(this);
// }
void print(){
cout<<"print"<<endl;
}
~Widget()
{
cout<<"~Widget()"<<endl;
}
void invoke(){
process(shared_ptr<Widget>(this));
}
};
void process( std::shared_ptr<Widget> tp)
{
tp->print();
cout<<"ref count: "<<tp.use_count()<<endl;
cout<<"process--------"<<endl;
}
int main() {
shared_ptr<Widget> sw1;
{
Widget* pw=new Widget();
sw1=shared_ptr<Widget>(pw);//
shared_ptr<Widget> sw2=sw1->getWidget(); // getWidget(pw);
}
}
上面代码
return shared_ptr<Widget>(this);
sw1=shared_ptr<Widget>(pw);//
shared_ptr<Widget> sw2=sw1->getWidget(); // getWidget(pw);
return shared_ptr<Widget>(this); 的问题同一个堆对象,创建了两个share_ptr
但是这两个share_ptr的引用监控 不是同一个,各自的,两套引用计数控制,所以会导致重复删除!!!
代码3
如果遇到以下场景,怎么应对?
void invoke(){
process(shared_ptr<Widget>(this));
}
void process( std::shared_ptr<Widget> tp)
{
tp->print();
cout<<"ref count: "<<tp.use_count()<<endl;
cout<<"process--------"<<endl;
}
**怎么解决上述三段代码的问题? enalbe_shared_from_this<> **
class Widget;
void process(const std::shared_ptr<Widget> tp);
//1. 必须公有继承,确保Widget可转型为enable_shared_from_this
//2. 必须使用Shared指针,调用 shared_from_this()
// 安全做法:将构造函数private禁止创建栈对象、使用静态工厂函数确保创建shared指针
class Widget : public std::enable_shared_from_this<Widget> {
public:
std::shared_ptr<Widget> getWidget() {
// if(weak_from_this().expired())
// {
// cout<<"oops, expired..."<<endl;
// }
return shared_from_this(); // OK
//return shared_ptr<Widget>(this);
}
void invoke(){
process(shared_from_this());
//process(shared_ptr<Widget>(this));错误!
}
void print(){
cout<<"print"<<endl;
}
~Widget()
{
cout<<"~Widget()"<<endl;
}
//工厂函数
static std::shared_ptr<Widget> create() {
return std::shared_ptr<Widget>(new Widget());
}
private:
Widget()=default;//这个避免用户创建栈对象
// 这个避免用户创建栈对象,因为栈对象的时候 shared_from_this(),智能指针封装的栈对象指针,不允许,
// 所以要屏蔽默认构造函数,必须通过工厂方法 获得 堆对象的智能指针,针对就是下面三行代码的情景
// Widget w1;
// shared_ptr<Widget> w2=w1.getWidget();// &w1
// cout<<"ref count: "<<w2.use_count()<<endl;
};
void process(const std::shared_ptr<Widget> tp)
{
tp->print();
cout<<"ref count: "<<tp.use_count()<<endl;
cout<<"process--------"<<endl;
}
int main() {
{
// Widget* p=new Widget();
// shared_ptr<Widget> sp1 {p};
// auto sp2 = sp1->getWidget(); //sp2 is a copy of sp1
//shared_ptr<Widget> sp2 {p};
// process(sp1);
// cout<<"ref count: "<<sp1.use_count()<<endl;
// cout<<"ref count: "<<sp2.use_count()<<endl;
}
{
// Widget w1;
// shared_ptr<Widget> w2=w1.getWidget();// &w1
// cout<<"ref count: "<<w2.use_count()<<endl;
// Widget* p=new Widget();// w1;
// shared_ptr<Widget> w2=p->getWidget();// &w1
// delete p;
}
{
shared_ptr<Widget> sp1=Widget::create();
//auto sp2=sp1->getWidget();
cout<<"ref count: "<<sp1.use_count()<<endl;
cout<<"ref count: "<<sp2.use_count()<<endl;
cout<< sizeof(Widget)<<endl;
}
}
最后我们通过VS编译器看看 share_ptr内的内存结构
简单代码
#include "iostream"
#include "memory"
class student {
public:
student(int age_):age(age_) {
std::cout << "student 构造函数 堆内存地址" << (int*)this << std::endl;
}
student(const student& stu) {
std::cout << "stu拷贝构造" << std::endl;
this->age = stu.age;
}
~student() {
std::cout << "析构" << std::endl;
}
void test() {
std::cout << "student test()>>" << age << std::endl;
}
int age;
private:
};
int main() {
student* p = new student(999);
std::shared_ptr<student> ptr(p);
std::cout << sizeof(ptr) << std::endl;
int* p1 = (int*)(&ptr);
std::cout << (int *)*p1 << std::endl; // 取出的就是student对象堆地址
if (p1 == nullptr) {
std::cout << "null" << std::endl;
}
else {
//转成student 指针,
student* s = (student*)(*p1);
s->test();
}
p1++;//定位到控制块指针
std::cout << (int *)*p1 << std::endl; //控制块地址
return 1;
}