一、原始指针常见的内存问题

手动管理原始指针时,极易出现以下 6 类致命问题,也是智能指针要解决的核心痛点:

  • 资源释放了,指针本身未置空
  • 野指针:指向未初始化内存,或只有一个指针指向已被释放的资源
  • 悬垂指针:多个指针指向同一个已被释放的资源,其中一个释放后其余全部失效
  • 踩内存:指针指向的内存已被其他数据覆盖,访问时会读到错误数据或破坏正常数据
  • 双重释放:多次调用delete释放同一个指针指向的地址,会破坏堆内存管理结构
  • 未释放资源:忘记调用delete,导致内存持续占用,长期运行会耗尽系统内存

二、智能指针核心原理

一句话总述:智能指针是 C++11 引入的、封装原始指针的模板类(位于<memory>头文件),严格遵循 **RAII(资源获取即初始化)** 思想,利用对象的生命周期自动管理动态资源的释放,无需手动调用delete,从根本上避免上述所有内存问题。C++ 标准库提供了三种核心智能指针,分工明确:std::unique_ptr(独占所有权)、std::shared_ptr(引用计数共享所有权)和std::weak_ptr(辅助shared_ptr解决循环引用)。

  • RAII 核心逻辑:利用对象的生命周期绑定程序资源的生命周期
  • 智能指针的实现基础:在构造函数中获取并接管资源,在析构函数中自动释放资源
  • 额外优势:重载了operator*operator->,提供与原始指针几乎一致的使用体验

三、三种智能指针详解

1. std::shared_ptr —— 共享所有权

核心一句话std::shared_ptr实现共享所有权语义,通过底层引用计数机制允许多个指针共同管理同一资源,核心解决悬垂指针和资源跨作用域共享的问题。

语义:共享所有权

  • 资源没有明确的单一所有者,可能被多个对象、函数或线程同时操作
  • 资源的生命周期由所有指向它的shared_ptr共同决定

原理:使用引用计数机制

  • 每新增一个shared_ptr指向资源,引用计数 + 1
  • 每销毁一个shared_ptr(离开作用域或被重置),引用计数 - 1
  • 只有当引用计数归 0时,才会自动调用析构函数释放资源
  • 底层维护独立的控制块:存储强引用计数、弱引用计数、自定义删除器和指向资源的原始指针

核心使用场景

  1. 在容器中管理指针

    class T{};
    // 原始写法:clear()只释放栈上的指针数组,堆上的T对象未释放,必须手动遍历delete
    vector<T*> vec; 
    // 智能指针写法:调用clear()时,会自动析构所有T对象,无需手动处理
    vector<shared_ptr<T>> vec; 
    
  2. 资源跨函数传递

    // 原始写法:func中可能因逻辑分支遗漏、抛出异常等情况未执行delete,造成内存泄漏
    {
        int *p = new int;
        func(p); 
    }
    // 智能指针写法:shared_ptr生命周期结束时自动释放资源,异常安全
    {
        auto p = make_shared<int>();
        func(p); 
    }
    

严格使用规范:使用shared_ptr管理动态资源时,绝对不要混用原始裸指针

  1. 构造智能指针时,不要暴露裸指针

    // ❌ 不推荐:裸指针p被暴露,可能被外部意外delete,导致智能指针析构时重复释放
    int *p = new int();
    shared_ptr<int> sp = shared_ptr<int>(p); 
    // ✅ 较好:隐藏了裸指针,避免外部误操作
    shared_ptr<int> sp = shared_ptr<int>(new int); 
    // ✅ 最优:使用make_shared构造(一次性分配对象和控制块内存,更安全高效,自动维护弱引用计数)
    shared_ptr<int> sp = make_shared<int>(); 
    

    补充:用裸指针构造shared_ptr时,对象和控制块是分开分配的,且不会自动维护弱引用计数;make_shared则一次性完成所有分配,性能和安全性更优。

  2. 不要使用get()接口获取裸指针

    int* p = sp.get(); // ❌ 绝对禁止:手动操作p会导致重复释放、踩内存、野指针等问题
    
  3. 不要由同一个裸指针构造多个智能指针对象

    • 本质是多个独立的智能指针控制块管理同一块资源,会导致重复析构
    • 常见错误:在类中用this直接构造shared_ptr返回自身
      // ❌ 错误写法
      class T{
      public:   
          shared_ptr<T> self(){
              return shared_ptr<T>(this); 
          } 
      };
      // ✅ 正确写法:继承enable_shared_from_this模板类,调用shared_from_this()返回
      class T:public enable_shared_from_this<T>{
      public:   
          shared_ptr<T> self(){
              return shared_from_this(); 
          } 
      };
      

2. std::unique_ptr —— 独占所有权

核心一句话std::unique_ptr实现独占所有权语义,同一时刻仅一个指针拥有资源的所有权,禁止拷贝仅支持移动,核心解决野指针和重复释放问题。

语义:独享所有权

  • 资源有固定且唯一的拥有者,不希望有多个指针同时指向和操作它
  • 所有权可以转移,但转移后原指针立即失效

特点

  • 拷贝构造函数和赋值运算符被显式delete,禁止拷贝
  • 仅提供移动构造移动赋值,通过std::move转移所有权
  • 无需维护引用计数,性能几乎与原始指针一致,开销极小

使用场景:资源的所有权明确且唯一的场景(如工厂函数返回值、局部动态对象、容器内的非共享对象)

使用规范

  • 不支持拷贝,但是可以从函数中返回一个unique_ptr
    class T1{};
    unique_ptr<T1> get_unique(){
        unique_ptr<T1> up = make_unique<T1>();
        return up;
    }
    // 调用
    unique_ptr<T1> up = get_unique();
    

    补充:编译器会优先进行返回值优化(RVO/NRVO),直接在调用方构造对象;如果关闭编译器优化,会按以下优先级调用:1. 移动构造 2. 拷贝构造 3. 没有拷贝构造则编译报错

  • 推荐使用std::make_unique构造(C++14 引入,C++11 需自行实现)

3. std::weak_ptr —— 弱引用

核心一句话std::weak_ptr是不拥有资源所有权的弱引用指针,不会增加强引用计数,仅作为 "观察者" 存在,专门辅助shared_ptr解决循环引用导致的内存泄漏问题。

循环引用问题示例

class B;
class A{
public:
    ~A(){
        cout<<"A::~A()"<<endl; 
    }
    shared_ptr<B> spb; // A持有B的强引用
};

class B{
public:
    ~B(){
        cout<<"B::~B()"<<endl; 
    }
    shared_ptr<A> spa; // B持有A的强引用
};

void func(){
    shared_ptr<A> sp1 = make_shared<A>();
    shared_ptr<B> sp2 = make_shared<B>();
    sp1->spb = sp2; // sp2引用计数变为2
    sp2->spa = sp1; // sp1引用计数变为2
}
  • 问题:离开func作用域后,sp1sp2被销毁,两者的引用计数都减为 1,永远无法归 0,导致 A 和 B 的资源永远无法释放,造成内存泄漏
  • 解决:将其中一方的强引用改为weak_ptr,弱引用不占用强引用计数,从而打破循环引用链

核心特点

  • 不拥有资源所有权,不能直接解引用访问资源
  • 仅能由shared_ptr或另一个weak_ptr构造
  • 可通过expired()方法检查资源是否已被释放
  • 可通过lock()方法获取一个临时的shared_ptr(若资源未释放),用于安全访问资源

四、智能指针补充说明与使用要点

其他相关智能指针

  • std::auto_ptr<T>(C++98,已弃用):具有浅拷贝语义,拷贝时会自动转移所有权,极易导致悬空指针问题,已在 C++11 被std::unique_ptr完全取代
  • std::scoped_ptr(Boost 库):与std::unique_ptr类似,但不支持移动语义,仅能在封闭作用域内使用

通用使用要点

  1. 避免裸指针混用:资源一旦交给智能指针管理,就不要再通过裸指针操作它
  2. 按需选择类型:优先使用unique_ptr(性能最高);只有需要共享资源时才用shared_ptr;存在循环引用风险时必须搭配weak_ptr
  3. 自定义删除器unique_ptrshared_ptr都支持传入自定义删除器(函数对象、lambda 表达式等),可用于管理文件句柄、网络连接、数组等非new分配的资源
  4. 性能考虑unique_ptr无额外开销,适合高性能场景;shared_ptr的引用计数操作是原子的,有一定的线程安全开销,慎用在性能敏感的高并发场景

五、完整示例代码

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

struct Resource {
    Resource(int v) : value(v) {
        cout << "构造 Resource, value = " << value << endl;
    }
    ~Resource() {
        cout << "析构 Resource, value = " << value << endl;
    }
    int value;
};

int main() {
    cout << "== unique_ptr 示例 ==\n";
    {
        // 创建 unique_ptr,通过 make_unique 更安全
        auto up = make_unique<Resource>(10);
        cout << "up->value = " << up->value << endl;

        // 无法复制,只能移动所有权
        // auto up2 = up; // 编译错误
        auto up2 = move(up);
        if (!up) {
            cout << "up 已为空,所有权转移给 up2" << endl;
        }
        // up2 离开作用域后自动删除 Resource
    }

    cout << "\n== shared_ptr & weak_ptr 示例 ==\n";
    {
        // 创建 shared_ptr,通过 make_shared 更高效
        auto sp1 = make_shared<Resource>(20);
        cout << "sp1.use_count() = " << sp1.use_count() << endl;

        {
            auto sp2 = sp1; // 共享所有权,引用计数加 1
            cout << "sp1.use_count() = " << sp1.use_count() << endl;

            // 创建 weak_ptr,不增加引用计数
            weak_ptr<Resource> wp = sp1;
            cout << "wp.expired()? " << boolalpha << wp.expired() << endl;

            if (auto temp = wp.lock()) {
                cout << "通过 weak_ptr 获得临时 shared_ptr, value = " << temp->value << endl;
            }
            // sp2 离开内层作用域后引用计数减 1
        }

        cout << "sp1.use_count() = " << sp1.use_count() << endl;
        // sp1 离开作用域后,引用计数为 0,Resource 被析构
    }

    cout << "\n== shared_ptr 循环引用示例(使用 weak_ptr 解决) ==\n";
    struct Node {
        int id;
        shared_ptr<Node> next;
        weak_ptr<Node> prev; // 用 weak_ptr 避免循环引用
        Node(int i) : id(i) {
            cout << "构造 Node " << id << endl;
        }
        ~Node() {
            cout << "析构 Node " << id << endl;
        }
    };

    {
        auto n1 = make_shared<Node>(1);
        auto n2 = make_shared<Node>(2);
        n1->next = n2;       // n1 持有 n2 的强引用
        n2->prev = n1;       // n2 持有 n1 的弱引用,不增加引用计数

        // 离开作用域后,n2、n1 的引用计数都能归零,依次析构
    }

    cout << "程序结束\n";
    return 0;
}

/*
运行结果:
== unique_ptr 示例 ==
构造 Resource, value = 10
up->value = 10
up 已为空,所有权转移给 up2
析构 Resource, value = 10

== shared_ptr & weak_ptr 示例 ==
构造 Resource, value = 20
sp1.use_count() = 1
sp1.use_count() = 2
wp.expired()? false
通过 weak_ptr 获得临时 shared_ptr, value = 20
sp1.use_count() = 1
析构 Resource, value = 20

== shared_ptr 循环引用示例(使用 weak_ptr 解决) ==
构造 Node 1
构造 Node 2
析构 Node 1
析构 Node 2
程序结束
*/

代码说明:

  • unique_ptr 部分:展示了独占所有权、移动语义和自动析构特性
  • shared_ptr & weak_ptr 部分:展示了引用计数的变化、weak_ptr 的观察和临时访问能力
  • 循环引用部分:对比了使用 shared_ptr 和 weak_ptr 的差异,验证了 weak_ptr 解决循环引用的有效性

六、核心总结表

智能指针类型 所有权 引用计数 核心解决问题 典型使用场景 性能开销
unique_ptr 独占 野指针、重复释放 单一所有权资源、工厂函数返回值 极低
shared_ptr 共享 悬垂指针、资源跨作用域共享 多对象共享资源、容器管理指针 中等
weak_ptr shared_ptr 循环引用 双向链表、父子对象互相引用 极低

其他问题

1. make_shared 相比直接用 new 构造 shared_ptr 的优缺点
  • 优点(必答):
    • 一次性分配对象和控制块内存,减少一次内存分配,性能更高
    • 异常安全:如果构造函数抛出异常,不会导致内存泄漏
    • 自动维护弱引用计数,避免weak_ptr悬空的极端情况
  • 缺点(加分项):
    • 无法使用自定义删除器
    • 大对象可能导致内存碎片
    • 当所有shared_ptr销毁但还有weak_ptr存在时,整个内存块(对象 + 控制块)不会被释放,直到最后一个weak_ptr销毁
2. shared_ptr 的线程安全问题
  • 结论shared_ptr引用计数操作是线程安全的(原子操作),但对象本身的读写不是线程安全的
  • 举例:
    • 多个线程同时拷贝同一个shared_ptr是安全的(引用计数原子增减)
    • 多个线程同时修改同一个shared_ptr指向的对象是不安全的(需要加锁)
    • 多个线程同时让同一个shared_ptr指向不同的对象是不安全的
3. enable_shared_from_this 的实现原理
  • 为什么不能直接用this构造shared_ptr:会生成两个独立的控制块,导致重复析构
  • 实现原理:enable_shared_from_this内部有一个weak_ptr成员,当第一个shared_ptr构造时,会自动初始化这个weak_ptr
  • shared_from_this()本质是从内部的weak_ptr调用lock()生成一个新的shared_ptr,共享同一个控制块

4. unique_ptr 如何支持数组
  • C++11 起unique_ptr支持数组特化:std::unique_ptr<int[]> arr(new int[10]);
  • 数组特化的unique_ptr会自动调用delete[]释放资源
  • 注意:shared_ptr不支持数组特化(C++17 前),如果用shared_ptr管理数组,必须传入自定义删除器:
    std::shared_ptr<int> arr(new int[10], [](int* p){ delete[] p; });
    
5. weak_ptrexpired()lock() 的区别
  • expired():检查资源是否已被释放,返回bool
  • lock():如果资源未释放,返回一个有效的shared_ptr;否则返回空的shared_ptr
  • 最佳实践:永远不要先调用expired()再调用lock()(多线程下可能存在竞态条件),直接用lock()
    // ❌ 错误:expired()和lock()之间资源可能被释放
    if (!wp.expired()) {
        auto sp = wp.lock();
        // 操作sp
    }
    // ✅ 正确:原子操作
    if (auto sp = wp.lock()) {
        // 操作sp
    }
    
6. 智能指针的自定义删除器
  • 什么时候用:管理非new分配的资源(文件句柄、socket、数据库连接、malloc 分配的内存)
  • unique_ptr的删除器是类型的一部分:std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("test.txt", "r"), fclose);
  • shared_ptr的删除器不是类型的一部分,更灵活:std::shared_ptr<FILE> fp(fopen("test.txt", "r"), fclose);

7. shared_ptr 的控制块里到底有什么
  • 强引用计数(use_count()
  • 弱引用计数(weak_count()
  • 自定义删除器(如果有)
  • 指向资源的原始指针
  • 对齐填充(为了内存对齐)
8. 什么是「智能指针的类型转换」
  • static_pointer_cast:静态转换,对应static_cast
  • dynamic_pointer_cast:动态转换,对应dynamic_cast(用于多态类型)
  • const_pointer_cast:常量转换,对应const_cast
  • 注意:不能用普通的static_cast直接转换智能指针,必须用上述专用函数

常见面试题

C++ 智能指针有哪些?它们的区别是什么?

C++ 主要有 3 种智能指针(位于<memory>头文件中):

  1. std::unique_ptr<T>(独占所有权)

    • 不能被复制,只能移动(std::move)。
    • 适用于独占资源管理(如文件、网络连接)。
    • std::make_unique<T>(args...)创建(C++14+)。
  2. std::shared_ptr<T>(共享所有权)

    • 采用引用计数,多个shared_ptr可共享同一对象,最后一个销毁时释放资源。
    • 存在循环引用风险,可配合std::weak_ptr解决。
    • std::make_shared<T>(args...)创建,减少内存分配开销。
  3. std::weak_ptr<T>(弱引用)

    • 依赖shared_ptr,不会增加引用计数。
    • 用于解决shared_ptr循环引用问题。
    • 可通过lock()获取shared_ptr,判断对象是否仍然有效。

手写shared_ptr

一、核心原理

手写shared_ptr的本质是:用两个指针实现 "共享所有权"

  • 一个指针T* ptr:直接指向你要管理的堆对象(方便快速解引用)
  • 另一个指针ShareCount<T>* countPtr:指向一个堆上的共享计数器对象
  • 所有指向同一个堆对象的shared_ptr,都共享同一个计数器
  • 计数器记录当前有多少个shared_ptr指向这个对象:
    • 新增一个持有者 → 计数 + 1
    • 一个持有者销毁 / 重置 → 计数 - 1
    • 计数减到 0 → 自动释放堆对象 + 释放计数器本身

二、逐模块代码解析

1. ShareCount 类:专门管引用计数和资源释放(最核心的底层)

这是整个实现的灵魂,所有的计数逻辑和资源释放都封装在这里,对外不可见。

template<typename T>
class ShareCount {
private:
    T* ptr;       // 指向真正的堆对象
    int count;    // 引用计数:记录有多少个shared_ptr指向它

    // 禁止拷贝和赋值:计数器绝对不能被拷贝,必须全局唯一
    ShareCount(const ShareCount&) = delete;
    ShareCount& operator=(const ShareCount&) = delete;

public:
    // 构造函数:第一个shared_ptr创建时,计数初始化为1
    ShareCount(T* p) : ptr(p), count(1) {}

    // 析构函数:真正释放堆对象的地方
    ~ShareCount() { delete ptr; }

    // 新增一个持有者:计数+1
    void increment() { count++; }

    // 减少一个持有者:计数-1,到0就销毁自己和堆对象
    void decrement() {
        count--;
        if (count == 0) {
            delete this; // 先调用自己的析构函数(释放堆对象),再释放计数器本身
        }
    }

    T* get() const { return ptr; }
};

2. shared_ptr 类:对外的智能指针接口

封装了两个指针,提供和原始指针一样的使用体验(->*),同时自动管理生命周期。

template<typename T> 
class shared_ptr {
private:
    T* ptr;                // 指向堆对象
    ShareCount<T>* countPtr; // 指向共享计数器

public:
    // -------------------------- 构造函数 --------------------------
    // 普通构造:传入堆上的原始指针,创建计数器
    shared_ptr(T* p = nullptr) : ptr(p), countPtr(nullptr) {
        if (p) {
            countPtr = new ShareCount<T>(p);
        }
    }

    // 拷贝构造:多个shared_ptr共享同一个对象和计数器
    shared_ptr(const shared_ptr& other) : ptr(other.ptr), countPtr(other.countPtr) {
        if (countPtr) {
            countPtr->increment(); // 计数+1
        }
    }

    // 移动构造:转移所有权,原对象变成空指针(计数不变)
    shared_ptr(shared_ptr&& other) noexcept : ptr(other.ptr), countPtr(other.countPtr) {
        other.ptr = nullptr;
        other.countPtr = nullptr;
    }

    // -------------------------- 析构函数 --------------------------
    ~shared_ptr() {
        if (countPtr) {
            countPtr->decrement(); // 计数-1,到0自动释放资源
        }
    }

    // -------------------------- 运算符重载 --------------------------
    // 让智能指针对象能像原始指针一样用->
    T* operator->() const {
        return ptr;
    }

    // 让智能指针对象能像原始指针一样用*解引用
    T& operator*() const {
        return *ptr;
    }

    // -------------------------- 核心方法 --------------------------
    // reset:重置当前智能指针,指向新对象(或空)
    void reset(T* p = nullptr) {
        if (p != ptr) { // 避免自己reset自己
            // 第一步:先释放原来的引用
            if (countPtr) {
                countPtr->decrement();
            }
            // 第二步:指向新的对象
            ptr = p;
            if (p) {
                countPtr = new ShareCount<T>(p); // 新对象创建新计数器
            } else {
                countPtr = nullptr;
            }
        }
    }

    // get:获取原始指针(和C接口兼容)
    T* get() const {
        return ptr;
    }

    // -------------------------- 赋值运算符(必须补全) --------------------------
    // 拷贝赋值
    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) { // 防止自赋值
            if (countPtr) countPtr->decrement(); // 释放当前资源
            ptr = other.ptr;
            countPtr = other.countPtr;
            if (countPtr) countPtr->increment(); // 计数+1
        }
        return *this;
    }

    // 移动赋值
    shared_ptr& operator=(shared_ptr&& other) noexcept {
        if (this != &other) {
            if (countPtr) countPtr->decrement(); // 释放当前资源
            ptr = other.ptr;
            countPtr = other.countPtr;
            other.ptr = nullptr;
            other.countPtr = nullptr;
        }
        return *this;
    }
};

三、结合 main 函数走完整流程

我们一步步看main 函数执行时,内存和计数的变化:

int main() {
    // 1. 创建ptr1,指向new int(10)
    shared_ptr<int> ptr1(new int(10)); 
    // 内存状态:
    // ptr1.ptr = 0x1000(指向int值10)
    // ptr1.countPtr = 0x2000(指向ShareCount对象)
    // ShareCount(0x2000):ptr=0x1000, count=1

    // 2. 拷贝构造ptr2,和ptr1共享同一个对象
    shared_ptr<int> ptr2 = ptr1; 
    // 调用拷贝构造:
    // ptr2.ptr = 0x1000,ptr2.countPtr = 0x2000
    // countPtr->increment() → count=2

    // 输出都是10,因为指向同一个对象
    std::cout << "ptr1 :" << *ptr1 << std::endl; // 10
    std::cout << "ptr2 :" << *ptr2 << std::endl; // 10

    // 3. ptr1调用reset(),放弃所有权
    ptr1.reset(); 
    // 调用reset:
    // countPtr->decrement() → count=1
    // ptr1.ptr = nullptr,ptr1.countPtr = nullptr
    // 注意:ptr2还持有所有权,所以int对象和计数器都没被释放

    // ptr2仍然能正常访问
    std::cout << "ptr2 :" << *ptr2 << std::endl; // 10

    // 4. 移动构造ptr3,把ptr2的所有权转移给ptr3
    shared_ptr<int> ptr3 = std::move(ptr2); 
    // 调用移动构造:
    // ptr3.ptr = 0x1000,ptr3.countPtr = 0x2000
    // ptr2.ptr = nullptr,ptr2.countPtr = nullptr
    // 计数还是1(只是换了个持有者,总数没变)

    // ptr3正常访问
    std::cout << "ptr3 :" << *ptr3.get() << std::endl; // 10

    // 5. main函数结束,所有局部变量按创建顺序逆序销毁
    // 销毁ptr3:调用~shared_ptr() → countPtr->decrement() → count=0
    // 触发ShareCount的decrement:
    //   先调用~ShareCount() → delete 0x1000(释放int 10)
    //   再delete this → 释放0x2000(释放计数器)
    // 销毁ptr2:countPtr是nullptr,什么都不做
    // 销毁ptr1:countPtr是nullptr,什么都不做
    // ✅ 所有内存都被正确释放,没有泄漏!
}

极简面试版

template <typename T> // 模板,支持int、string、自定义类等任意类型
class shared_ptr {
private:
    T* _ptr;    // 1. 指向真正的堆对象(比如你new出来的int、点云、图像)
    int* _count; // 2. 指向共享的引用计数(最核心的设计!)

public:
    // 普通构造:创建第一个智能指针
    shared_ptr(T* p) : _ptr(p), _count(new int(1)) {}

    // 拷贝构造:多个智能指针共享同一个对象
    shared_ptr(const shared_ptr& other) {
        _ptr = other._ptr;    // 大家指向同一个对象
        _count = other._count; // 大家指向同一个计数
        (*_count)++;          // 多了一个持有者,计数+1
    }

    // 析构函数:智能指针离开作用域时自动调用
    ~shared_ptr() {
        (*_count)--; // 我走了,计数-1
        if (*_count == 0) { // 没人用了,彻底释放
            delete _ptr;   // 释放堆对象
            delete _count; // 释放计数本身
        }
    }

    // 让智能指针用起来和原始指针一模一样
    T& operator*() { return *_ptr; } // 支持 *p 解引用
    T* operator->() { return _ptr; } // 支持 p->xxx 访问成员
};

std::make_shared 相比std::shared_ptr<T>(new T(args...))有什么好处?

使用 std::make_shared<T>(args...) 相比 std::shared_ptr<T>(new T(args...)) 主要有以下几个好处:

  1. 避免额外的内存分配

    • std::make_shared 会在一次内存分配中同时分配对象本体和引用计数,而 std::shared_ptr<T>(new T(args...)) 需要两次分配(一次给 T,一次给 shared_ptr 的控制块)。
    • 这不仅减少了 malloc/free 的开销,还能提高缓存命中率。
  2. 减少异常安全问题

    • std::shared_ptr<T>(new T(args...)) 是两个独立的操作,new T(args...) 可能会抛出异常,而 shared_ptr 还未成功构造,导致内存泄漏。
    • std::make_shared 进行的是原子操作,不存在这个问题。
  3. 更高效的引用计数管理

    • 由于 std::make_shared 在一个内存块中存储对象和引用计数,指针访问时可以减少额外的缓存访问,提高运行效率。
    • std::shared_ptr<T>(new T(args...)) 由于分开分配对象和控制块,会导致额外的指针间接访问。
  4. 代码更简洁

         auto ptr = std::make_shared<T>(args...)auto ptr = std::shared_ptr<T>(new    T(args...)) 更简短,可读性更好。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐