shared_ptr使用场景、陷阱、性能分析、使用建议[转]

shared_ptr使用场景、陷阱、性能分析、使用建议[转]

目录

shared_ptr使用场景、陷阱、性能分析、使用建议[转]

std::shared_ptr 使用场景

std::shared_ptr 使用陷阱分析

慎用裸指针

慎用get()返回的指针

enable_shared_from_this

避免循环引用

性能说明

尺寸问题

移动语义

补充说明和使用建议

std::shared_ptr 使用场景

shared_ptr<int> create0(int value){
	return make_shared<int>(value); //返回一个shared_ptr
}

void myfunc(int value){
	shared_ptr<int> ptmp = create0(10);
	return; // 离开作用域后, ptemp会被自动释放,它所指向的内存也会被自动释放
}

shared_ptr<int> myfunc1(int value){
	shared_ptr<int> ptmp = create0(10);
	return ptmp; // 系统是根据ptmp这个局部变量来产生一个临时的shared_ptr对象往回返
}
myfunc(12); 
// 如果这块不用shared_ptr变量来接收myfunc返回的结果,那么从myfunc返回的shared_ptr就会被销毁,所指向的对象也会被销毁。 

auto p11 = myfunc1(12); 
// 我们用了一个变量来接myfunc的返回值,那么myfunc1返回的shared_ptr就不会被销毁,它所指向的对象也不会被销毁。

std::shared_ptr 使用陷阱分析

慎用裸指针

void proc(shared_ptr<int> ptr){
	return;
}
int *p = new int(100); //裸指针
//proc(p); //语法错, int* p 不能转换成shared_ptr<int>

proc(shared_ptr<int>(p)); // 参数是个临时的shared_ptr, 用一个裸指针显式的构造;
*p = 45; // 潜在的不可预料的问题; 因为p指向的内存已经被释放了;
  • 下面方式可行:
​int *p = new int(100); //裸指针
shared_ptr<int> p2(p);
proc(p2);
*p2 = 45;
  • 小结:把一个普通裸指针绑定到了一个shared_ptr上之后,那内存管理的责任就交给了shared_ptr了,这个时候你就不应该再用裸指针(内置指针)来访问shared_ptr 所指向的内存了;
shared_ptr<int> myp(new int(100));
proc(myp);
*myp = 45;
  • 绝对要记住,不要用裸指针初始化多个shared_ptr
int *p1 = new int(100); // 裸指针
shared_ptr<int> p3(p1);
shared_ptr<int> p4(p1); // p1 和 p4 无关联了(p3和p4每个强引用计数都为1了),会导致p3和p4所指向的内存被释放两次,产生异常
shared_ptr<int> p5(new int);
shared_ptr<int> p6(p5); 
// 这种写法就是ok的,p5和p6 指向同一个内存地址并且两者是互通(用的是同一个控制块)

慎用get()返回的指针

  • 返回智能指针指向的对象所对应的裸指针(有些函数接口可能只能使用裸指针)
  • get返回的指针不能delete,否则会异常
shared_ptr<int> myp1(new int(100));
int *p00 = myp1.get();
//delete p00; // 不可以删除,会导致异常
  • 不能将其他智能指针绑定到get返回的指针上
shared_ptr<int> myp2(new int(100));
int *p22 = myp2.get(); //这个指针千万不能随意释放,否则myp2就没办法正常管理该指针了
{
	shared_ptr<int> myp3(p22); // 现在myp2和myp3引用计数都为1,但一旦跳出这个程序块
                              //离开上边这个myp3的范围,导致myp2指向的内存被释放了
}

myp2 = 65; // 该内存已经被释放,这样赋值会导致不可预料的后果;
  • 结论:永远不要用get得到的指针来初始化另外一个智能指针或者给另外一个智能指针赋值。

enable_shared_from_this

  • 不要把类对象指针(this) 作为shared_ptr返回,改用enable_shared_from_this
class CT {
public:
	shared_ptr<CT> getself(){
		return shared_ptr<CT>(this); //用裸指针初始化了多个shared_ptr的感觉;
	}
};
shared_ptr<CT> pct1(new CT);
//shared_ptr<CT> pct2 = pct1; //这是两个强引用;
shared_ptr<CT> pct2 = pct1->getself(); //问题出现
  • 应改成:
class CT : enable_shared_from_this<CT1>  {
public:
	shared_ptr<CT1> getself(){
		return shared_from_this(); // 这个就是enable_shared_from_this 类模板
	}
};
  • 现在,在外面创建CT对象的智能指针以及通过CT对象返回的this智能指针都是安全的;
  • 这个enable_shared_from_this中有一个弱指针weak_ptr,这个弱指针能够监视this,在我们调用shared_from_this()这个方法时,这个方法内部实际上是调用了这个weak_ptr的lock()方法;
  • 大家都知道lock方法会让shared_ptr指针计数+1, 同时返回这个shared_ptr , 这个就是工作原理;

避免循环引用

class CB; //声明一下CB

class CA{
public:
	shared_ptr<CB> m_pbs;
	~CA(){
		int test;
		test = 1;
	}
};

class CB{
public:
	shared_ptr<CA> m_pas;
	~CB(){
		int test;
		test = 1;
	}
};
shared_ptr<CA> pca(new CA);
shared_ptr<CB> pcb(new CB);

pca->m_pbs = pcb; // 等价于指向CB对象的有两个强引用
pcb->m_pas = pca; // 等价于指向CA对象的有两个强引用
  • 导致无法正常释放内存。
  • 改进方法:
class CB1; //声明一下CB

class CA1{
public:
	shared_ptr<CB1> m_pbs;
	~CA1(){
		int test;
		test = 1;
	}
};

class CB1{
public:
	weak_ptr<CA1> m_pas;  //把这里变成弱引用;
	~CB1(){
		int test;
		test = 1;
	}
};
shared_ptr<CA1> pca1(new CA1);
shared_ptr<CB1> pcb1(new CB1);

pca1->m_pbs = pcb1; // 等价于指向CB1对象的有两个强引用
pcb1->m_pas = pca1; // 因为m_pas 是弱引用,所以这里指向CA1的对象只有一个强引用;
  • 离开作用域之后,pca引用计数从1 就变成0 会释放CA1 对象(CA1的析构函数被执行);
  • CA1 的析构函数被执行了,(表示对象即将被释放),导致CA1内的m_pbs引用计数会减1, 也就是指向CB1对象的引用计数-1;
  • 超出pcb作用域时指向CB1 的计数也会-1;最终,会有一个时刻,指向CB1对象的强引用计数会从1减少到0,导致CB1对象被释放;
  • 上面代码运行,CA1先析构,CB1后析构;

性能说明

尺寸问题

  • shared_ptr的尺寸是裸指针的2倍; weak_ptr的尺寸是裸指针的2倍;
char *p111;
int ilenp111 = sizeof(p111); // 4字节
shared_ptr<string> p222;
int ilenp222 = sizeof(p222); // 8字节 ,包含两个裸指针的
  • a). 第一个裸指针指向的是这个智能指针所指向的对象
  • b). 第二个裸指针 指向一个很大的数据结构(控制块),这个控制块里边有啥:
    • b.1) 所指向对象的强引用计数: shared_ptr
    • b.2) 所指向对象的弱引用计数: weak_ptr
    • b.3) 其他数据,比如删除器指针,内存分配器;
  • 这个控制块是由第一个指向某个对象的shared_ptr来创建的;

  • 控制块创建时机:
    • a) make_shared : 分配并初始化一个对象,返回指向此对象的shared_ptr,所以,这个make_shared它总是能够创建一个控制块
shared_ptr<int> p333 = make_shared<int>(100);
  • b) 用裸指针来创建一个shared_ptr 对象时,
int *pi111 = new int();
shared_ptr<int> p555(pi111);
shared_ptr<int> p666(pi111); // 不允许,否则会产生多个控制块,也就是多个引用计数(每个都是1)析构时析构多次,导致异常;
shared_ptr<int> p777(new int);

移动语义

shared_ptr<int> p888(new int(100));
shared_ptr<int> p999(std::move(p888)); //移动语义, 移动构造一个新的智能指针对象p2,
										// p1 就不再指向该对象(变成空),引用计数依旧是1;

shared_ptr<int> p1000; 
p1000 = std::move(p999); // 移动赋值, p999指向空,p1000指向该对象,整个对象引用计数仍旧为1;
  • 移动肯定比复制快,复制你要增加引用计数,移动不需要;
  • 移动构造函数快过复制构造函数,移动赋值运算符快过拷贝赋值运算符;

补充说明和使用建议

  • a). 掌握了绝大部分shared_ptr用法;小部分没讲解,靠大家摸索。分配器,解决内存分配问题;
shared_ptr<int> p(new int), mydeleter(), mymallocator<int>()); ....
  • b). 谨慎使用,凡是没讲到过的用法:
    • new shared_ptr<T> , memcpy() 奇怪用法,大家不要轻易尝试。
  • c). 优先使用make_shared()
shared_ptr<string> ps1(new string("I Love China!")); // 分配两次内存
auto ps2 = make_shared<string>("I Love China!");// 分配1次内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值