【C++】std::make_shared函数的详解与示例

前言

在C++中,智能指针是一种非常重要的资源管理技术,用于自动管理动态分配的内存,以防止内存泄漏。std::shared_ptr是C++标准库中的一个智能指针类型,它通过使用引用计数来确保当最后一个shared_ptr被销毁或重置时,它所指向的对象也会被自动删除。而std::make_shared则是C++11引入的一个模板函数,用于更高效地创建std::shared_ptr实例。本文将详细解析std::make_shared函数的原理,并提供相关示例。

原理特点

单一内存分配

与直接使用new操作符创建对象并通过构造函数传递给std::shared_ptr相比,std::make_shared的最大优势在于它可以在一次内存分配中同时创建对象和控制块(control block)。控制块是std::shared_ptr内部用于管理引用计数、删除器和指向对象的指针的数据结构。通过使用单一分配,std::make_shared可以减少内存碎片,提高内存使用效率,并减少内存分配的开销。

引用计数

std::shared_ptr通过维护一个引用计数来跟踪有多少个shared_ptr实例指向同一个对象。每当一个新的shared_ptr指向该对象时,引用计数增加;每当一个shared_ptr被销毁或重置时,引用计数减少。当引用计数减少到0时,shared_ptr会自动调用其关联的删除器(默认为delete)来销毁对象并释放内存。

线程安全

std::shared_ptr的引用计数操作是线程安全的,这意味着多个线程可以同时操作同一个shared_ptr对象而不会导致数据竞争。然而,对shared_ptr所指向的对象的访问仍然需要适当的同步机制,如使用std::mutex。

示例

代码

#include <iostream>
#include <vector>
#include <memory>
#include <thread>
#include <mutex>

// 定义一个包含std::vector<int>的类
class ComplexObject {
   
   
public:
    // 成员函数:向vector中添加元素
    
<think>我们讨论的是`std::shared_ptr`的内存管理机制,特别是它如何自动释放内存以及析构机制。根据引用内容,我们可以总结如下: 1. **引用计数机制**:每个`std::shared_ptr`都有一个关联的计数器,称为引用计数(referencecount)。当进行拷贝操作(如用`shared_ptr`初始化另一个`shared_ptr`、传递`shared_ptr`作为函数参数或返回值)时,引用计数递增。当`shared_ptr`被销毁(例如离开作用域)或指向新对象时,引用计数递减。当引用计数变为0时,`shared_ptr`会自动释放所管理的对象[^1]。 2. **释放内存的时机**:引用计数为0时,`shared_ptr`的析构函数会删除其保存的指针所指向的对象。例如,在函数中创建了一个局部`shared_ptr`,当函数结束时,该指针离开作用域,引用计数减1。如果此时引用计数变为0,则对象被销毁[^1]。3.**多个`shared_ptr`共享所有权**:多个`shared_ptr`可以指向同一个对象,它们共同管理该对象的生命周期。只有当最后一个指向该对象的`shared_ptr`被销毁或重置时,对象才会被释放[^3]。4.**避免循环引用**:循环引用会导致引用计数无法归零,从而内存泄漏。解决方法是使用`std::weak_ptr`来打破循环[^4]。5.**自定义删除器**:`shared_ptr`允许指定自定义删除器,在释放对象时执行特定的清理操作(如关闭文件、释放其他资源等)[^3]。 下面详细解释:###1.引用计数机制`std::shared_ptr`使用引用计数来跟踪有多少个智能指针指向同一个对象。每次拷贝构造、赋值操作(使用`operator=`或拷贝构造函数)都会增加引用计数。例如: ```cppstd::shared_ptr<int>p1= std::make_shared<int>(42);//引用计数=1{std::shared_ptr<int> p2 =p1; //引用计数=2//离开作用域时,p2被销毁,引用计数减为1} //此时只有p1指向对象,引用计数=1``` 当引用计数变为0时,对象被删除。 ###2.内存释放过程当引用计数归零时,`shared_ptr`的析构函数会执行以下操作:1.调用所管理对象的析构函数(如果对象是类类型)。2.释放对象占用的内存(通过`delete`或自定义删除器)。 ###3.自定义删除器默认情况下,`shared_ptr`使用`delete`来释放内存。但可以指定自定义删除器: ```cppstruct FileCloser {voidoperator()(FILE* fp) const{if (fp)fclose(fp);}}; std::shared_ptr<FILE>fp(fopen("test.txt", "r"),FileCloser());``` 当引用计数归零时,`FileCloser`的`operator()`会被调用,关闭文件。###4.循环引用问题循环引用会导致内存泄漏: ```cppclass B; classA {public:std::shared_ptr<B>b_ptr; };class B{ public:std::shared_ptr<A> a_ptr;}; autoa= std::make_shared<A>();auto b= std::make_shared<B>(); a->b_ptr =b;// A引用B,B的引用计数=2b->a_ptr= a; //B引用A,A的引用计数=2```当`a`和`b`离开作用域时,它们的引用计数都减1(变为1),但不会归零,因此内存泄漏。解决方法:将其中一个成员改为`std::weak_ptr`。###5.`std::weak_ptr`的作用`weak_ptr`不增加引用计数,因此不会导致循环引用: ```cppclass B;class A{ public:std::weak_ptr<B> b_ptr;//使用weak_ptr打破循环}; ```###6.使用`make_shared`的优势`std::make_shared`在单次内存分配中同时创建对象和控制块(存储引用计数等),效率更高且更安全(避免因异常导致的内存泄漏)[^3]。 ###示例:引用计数的变化```cpp#include <iostream>#include <memory>int main() {autop1= std::make_shared<int>(10); //引用计数=1std::cout<< "p1count:" <<p1.use_count() <<std::endl;//输出1{auto p2 =p1; //引用计数=2std::cout<< "p2count:" <<p2.use_count() <<std::endl;//输出2} //p2离开作用域,引用计数减为1std::cout<< "p1count:" <<p1.use_count() <<std::endl;//输出1return0; }// p1离开作用域,引用计数归零,对象被释放```###总结- `std::shared_ptr`通过引用计数管理多个指针共享的对象。 -引用计数为0时自动释放对象。 -避免循环引用需结合`std::weak_ptr`。 -推荐使用`std::make_shared`创建`shared_ptr`。§§相关问题§§1.`std::shared_ptr`的引用计数机制是否是线程安全的?2.如何避免`std::shared_ptr`的循环引用问题?3. `std::make_shared`和直接使用`new`创建`shared_ptr`有何区别?4.自定义删除器在什么场景下使用?</think>### `std::shared_ptr`的内存管理机制 `std::shared_ptr`是C++11引入的智能指针,其核心是通过**引用计数机制**实现自动内存管理。以下是其工作原理和析构机制: --- #### 1. **引用计数机制** - 每个`shared_ptr`内部维护一个**控制块**(包含引用计数器和指向对象的指针) - **引用计数规则**: - 当`shared_ptr`被**拷贝**时:引用计数 $+1$ - 当`shared_ptr`被**销毁**(离开作用域)或**重置**时:引用计数 $-1$ - 当引用计数归零时:自动释放托管对象的内存 - 示例: ```cpp auto p1 = std::make_shared<int>(42); // 引用计数=1 { auto p2 = p1; // 拷贝构造,引用计数=2 } // p2销毁,引用计数=1 p1.reset(); // 引用计数归零,释放内存 ``` --- #### 2. **自动释放内存的时机** 内存释放发生在以下两种场景: 1. **最后一个`shared_ptr`离开作用域** ```cpp void func() { auto ptr = std::make_shared<MyClass>(); // 引用计数=1 } // ptr销毁 → 引用计数归零 → 对象析构 ``` 2. **显式调用`reset()`** ```cpp auto ptr = std::make_shared<MyClass>(); ptr.reset(); // 立即释放对象 ``` --- #### 3. **析构机制详解** 当引用计数归零时: 1. 调用托管对象的析构函数 ```cpp class MyClass { public: ~MyClass() { std::cout << "Destructor called\n"; } }; ``` 2. 释放对象占用的内存 3. 销毁控制块(释放引用计数器的内存) > **关键特性**:析构操作是**线程安全**的(引用计数操作使用原子操作)[^3] --- #### 4. **特殊场景处理** 1. **循环引用问题** ```cpp class Node { std::shared_ptr<Node> next; // 导致循环引用 }; auto a = std::make_shared<Node>(); auto b = std::make_shared<Node>(); a->next = b; b->next = a; // 引用计数永不为零 → 内存泄漏 ``` **解决方案**:使用`std::weak_ptr`打破循环[^4] 2. **自定义删除器** ```cpp auto file_ptr = std::shared_ptr<FILE>( fopen("data.txt", "r"), [](FILE* f) { if(f) fclose(f); } // 自定义删除器 ); ``` --- #### 5. **内存分配优化** - **`make_shared`的优势**: ```cpp auto p = std::make_shared<MyClass>(); // 推荐方式 ``` 1. 单次内存分配(对象+控制块) 2. 更好的缓存局部性 3. 异常安全[^1] - 对比`new`的劣势: ```cpp std::shared_ptr<MyClass> p(new MyClass); // 两次内存分配 ``` --- ### 总结 | 特性 | 说明 | |--------------------|----------------------------------------------------------------------| | 引用计数 | 多个指针共享对象所有权 | | 自动释放条件 | 引用计数归零时触发 | | 线程安全 | 引用计数操作为原子操作 | | 循环引用解决方案 | 使用`std::weak_ptr` | | 性能优化 | 优先使用`std::make_shared` | > 通过引用计数机制,`std::shared_ptr`实现了安全、自动的内存管理,是现代C++资源管理的核心工具之一[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木彳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值