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次内存

<think>好的,我现在需要详细介绍一下C++中的shared_ptr智能指针。首先,我应该从基本概念开始,解释什么是shared_ptr,它的作用和设计目的。然后,需要讨论它的核心机制,比如引用计数,所有权共享,以及如何避免内存泄漏。 接下来,我应该考虑构造和初始化的方法,比如直接构造、使用make_shared,或者通过其他指针或已有的shared_ptr来创建。这里要注意到make_shared的优势,比如内存分配效率。 然后,需要覆盖常用的成员函数,如use_count()、reset()、get()等,说明它们各自的用途和注意事项。特别是要强调不要轻易使用get()获取裸指针,以免引发悬挂指针的问题。 另外,自定义删除器的部分也很重要,说明为什么需要自定义删除器,以及如何使用它来处理特殊资源,比如文件指针或者malloc分配的内存。这里可能需要举例说明,比如传入一个lambda表达式作为删除器。 循环引用的问题必须提到,因为这是shared_ptr的常见陷阱。要解释循环引用是如何导致内存泄漏的,以及如何通过weak_ptr来打破循环。这部分需要结构清晰,可能用代码示例来演示问题及解决方案。 比较shared_ptr与其他智能指针,比如unique_ptr和weak_ptr的区别,帮助用户理解何时选择哪种指针。要强调所有权模型的不同,unique_ptr的独占性,而shared_ptr的共享性。 还需要注意线程安全的问题,说明shared_ptr的引用计数操作是原子性的,但指向的对象本身并不是线程安全的。这部分可能需要更详细的解释,或者举例说明在多线程环境下如何正确使用。 最后,总结最佳实践,比如优先使用make_shared,避免循环引用,不要混合使用裸指针和智能指针等。同时,可以提到一些常见的错误用法,帮助用户避免陷阱。 在组织内容时,要确保结构清晰,分点阐述,每个部分有明确的标题,便于用户理解。同时,要注意语言的口语化,避免过于技术性的术语,必要时用例子辅助说明。另外,检查是否有遗漏的重要点,比如shared_ptr的性能开销,或者与enable_shared_from_this的关系,如果有必要的话也应该简要提及。 现在,需要将这些内容整合成一个连贯的回答,确保覆盖所有关键点,同时保持逻辑流畅。可能需要先概述,再分章节详细说明,最后总结。注意用户要求用中文回答,所以术语要准确,但表达要自然易懂。</think>以下是对C++中`shared_ptr`智能指针的深度介绍,分模块逐步说明: --- ### 一、基本概念 `shared_ptr`是C++11引入的**共享所有权智能指针**,用于管理动态分配的内存。 - **核心特性**:通过引用计数机制,允许多个`shared_ptr`实例共享同一对象的所有权。 - **核心优势**:自动释放内存,避免内存泄漏;避免悬空指针问题。 --- ### 二、底层机制 #### 1. 引用计数 - 每个`shared_ptr`维护一个**控制块**(包含引用计数、弱引用计数、删除器、分配器等)。 - **规则**: - 构造或拷贝`shared_ptr`时,引用计数`+1`。 - 析构或`reset()`时,引用计数`-1`。 - 引用计数归零时,销毁对象并释放内存。 #### 2. 内存布局 ```cpp 对象内存 ───────────────┐ ↓ 控制块 [引用计数 | 弱引用计数 | 删除器 | 分配器] ``` --- ### 三、构造与初始化 #### 1. 直接构造 ```cpp std::shared_ptr<int> p1(new int(42)); // 不推荐:可能因异常导致内存泄漏 ``` #### 2. 推荐方式:`make_shared` ```cpp auto p2 = std::make_shared<int>(42); // 推荐:单次内存分配(对象+控制块) ``` #### 3. 从其他指针构造 ```cpp std::shared_ptr<int> p3(p1); // 共享所有权,引用计数+1 ``` --- ### 四、核心成员函数 | 函数 | 功能 | 示例 | |--------------------|-----------------------------------|---------------------------| | `use_count()` | 返回当前引用计数 | `p1.use_count()` | | `reset()` | 释放所有权,引用计数-1 | `p1.reset()` | | `get()` | 返回裸指针(谨慎使用!) | `int* raw = p1.get()` | | `swap()` | 交换两个`shared_ptr`内容 | `p1.swap(p2)` | --- ### 五、自定义删除器 用于管理非`new`分配的资源(如文件句柄、`malloc`内存等): ```cpp // 示例:管理 malloc 分配的内存 std::shared_ptr<void> p4(malloc(100), [](void* ptr) { free(ptr); std::cout << "内存已释放"; }); ``` --- ### 六、循环引用问题 #### 1. 问题场景 ```cpp class Node { public: std::shared_ptr<Node> next; }; auto a = std::make_shared<Node>(); auto b = std::make_shared<Node>(); a->next = b; b->next = a; // 循环引用,引用计数永不为0 → 内存泄漏! ``` #### 2. 解决方案:`weak_ptr` ```cpp class Node { public: std::weak_ptr<Node> next; // 弱引用不增加计数 }; ``` --- ### 七、与其他智能指针对比 | 类型 | 所有权模型 | 性能开销 | 适用场景 | |---------------|------------------|----------|------------------------| | `unique_ptr` | 独占所有权 | 无 | 单一所有者,资源移 | | `shared_ptr` | 共享所有权 | 较高 | 多所有者共享资源 | | `weak_ptr` | 无所有权(观察) | 低 | 解决循环引用 | --- ### 八、线程安全性 - **引用计数操作**:原子操作,线程安全。 - **对象访问**:需手动加锁(与裸指针相同)。 --- ### 九、最佳实践 1. **优先使用`make_shared`**:减少内存分配次数,避免异常安全问题。 2. **避免循环引用**:用`weak_ptr`打破循环。 3. **不要混用裸指针**: ```cpp int* raw = new int(10); std::shared_ptr<int> p5(raw); std::shared_ptr<int> p6(raw); // 错误!双重释放! ``` 4. **谨慎使用`get()`**:避免通过裸指针修改资源或延长生命周期。 --- ### 十、高级用法 #### 1. 别名构造(Alias Constructor) 允许`shared_ptr`指向某个对象的子部分,但共享整个对象的生命周期: ```cpp struct Data { int x, y; }; auto p7 = std::make_shared<Data>(); std::shared_ptr<int> p8(p7, &p7->x); // p8管理p7的x成员,但共享p7的控制块 ``` #### 2. `enable_shared_from_this` 在类的成员函数中安全获取自身的`shared_ptr`: ```cpp class MyClass : public std::enable_shared_from_this<MyClass> { public: void func() { auto self = shared_from_this(); // 正确获取shared_ptr } }; ``` --- ### 总结 `shared_ptr`通过引用计数实现自动内存管理,是C++中解决资源泄漏的核心工具之一。使用时需注意循环引用、线程安全和性能开销,合理结合`weak_ptr`和`unique_ptr`可构建高效、安全的内存管理体系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值