【c++】智能指针

【C++】智能指针 为什么需要智能指针呢?有了智能指针带来了什么问题?库里是如何解决的?就不得不说智能指针发展的历史。auto_ptr的解决方法、unique_ptr解决方法、shared_ptr解决方法。shared_ptr线程安全问题、循环引用问题。还有定制删除器。 阅读详情

前置基础:智能指针的核心使命

C++ 三大智能指针(std::unique_ptr、std::shared_ptr、std::weak_ptr,均位于 <memory> 头文件)。C++ 没有自动垃圾回收(GC),原生指针(raw pointer)的最大问题是手动管理内存容易导致泄漏、重复释放、悬垂指针。智能指针的核心使命是:封装原生指针,利用「RAII(资源获取即初始化)」机制,在对象析构时自动释放所管理的内存,无需手动调用 delete。

三大智能指针的底层都基于 RAII,但所有权管理方式不同,这也决定了它们的底层结构和性能差异。

一、std::unique_ptr:独占式智能指针(最简单、最高效)

1. 核心设计思想

unique_ptr 实现独占所有权:一个 unique_ptr 独占它所管理的原生指针,不允许其他智能指针共享该指针,也不允许拷贝(C++11 后支持移动,转移所有权)。正因为独占,它的底层结构最简单,无额外开销(与原生指针几乎等价)。

2. 底层核心数据结构(简化版)

unique_ptr 是一个模板类,底层仅封装两个核心成员(无额外堆内存开销,属于「栈上智能指针」):

template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
    T* ptr_; // 管理的原生指针(指向堆内存对象/数组)
    Deleter del_; // 删除器(默认 std::default_delete,负责释放内存)

    // 关键:禁用拷贝构造和拷贝赋值(保证独占性)
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

public:
    // 移动构造和移动赋值(允许转移所有权)
    unique_ptr(unique_ptr&& other) noexcept;
    unique_ptr& operator=(unique_ptr&& other) noexcept;

    // 其他核心接口...
};
关键细节拆解:
  1. 独占性的实现:通过显式删除拷贝构造和拷贝赋值运算符(= delete),禁止将一个 unique_ptr 拷贝给另一个,从语法上保证 “独占”。
  2. 删除器(Deleter):
    • 默认删除器 std::default_delete<T>:底层调用 delete ptr_(针对单个对象)或 delete[] ptr_(针对数组,unique_ptr<T[]> 特化版本)。
    • 自定义删除器:支持传入函数指针、函数对象、lambda 表达式,用于特殊场景(如释放共享内存、文件句柄)。
    • 删除器是模板参数,而非成员变量(C++11 优化):若使用默认删除器,del_ 不会占用额外内存(空基类优化,EBO),保证 unique_ptr 的大小与原生指针一致。
  3. 无额外堆内存:所有数据(ptr_、del_)都存储在栈上,析构时直接释放堆内存,无额外开销,是三大智能指针中性能最优的。

3. 核心接口底层实现

(1)构造函数
// 1. 默认构造:管理空指针
constexpr unique_ptr() noexcept : ptr_(nullptr), del_() {}

// 2. 原生指针构造(explicit 禁止隐式转换,避免意外)
explicit unique_ptr(T* ptr) noexcept : ptr_(ptr), del_() {}

// 3. 移动构造(转移所有权,other 变为空)
unique_ptr(unique_ptr&& other) noexcept 
    : ptr_(other.ptr_), del_(std::move(other.del_)) {
    other.ptr_ = nullptr; // 关键:将源对象的 ptr_ 置空,避免重复释放
}
(2)析构函数(核心:自动释放内存)
~unique_ptr() noexcept {
    if (ptr_ != nullptr) {
        del_(ptr_); // 调用删除器释放内存,默认是 delete ptr_
    }
}
  • 析构时仅判断 ptr_ 非空,然后调用删除器,无其他额外操作,效率极高。
  • 若管理的是数组(unique_ptr<T[]>),默认删除器会调用 delete[] ptr_,特化实现如下:
    template <typename T, typename Deleter>
    class unique_ptr<T[], Deleter> {
    private:
        T* ptr_;
        Deleter del_;
    public:
        ~unique_ptr() noexcept {
            if (ptr_ != nullptr) {
                del_(ptr_); // 默认 delete[] ptr_
            }
        }
    };
    
(3)移动赋值运算符
unique_ptr& operator=(unique_ptr&& other) noexcept {
    if (this != &other) { // 避免自赋值
        // 1. 先释放当前对象管理的内存(防止内存泄漏)
        if (ptr_ != nullptr) {
            del_(ptr_);
        }
        // 2. 转移 other 的所有权
        ptr_ = other.ptr_;
        del_ = std::move(other.del_);
        // 3. other 置空
        other.ptr_ = nullptr;
    }
    return *this;
}
(4)释放所有权(release ())
T* release() noexcept {
    T* temp = ptr_;
    ptr_ = nullptr; // 释放所有权,不释放内存
    return temp; // 返回原生指针,由调用者手动管理
}
  • 注意:release() 仅转移原生指针的所有权,不释放内存,与析构函数的核心区别。
(5)重置(reset ())
void reset(T* new_ptr = nullptr) noexcept {
    T* old_ptr = ptr_;
    ptr_ = new_ptr;
    if (old_ptr != nullptr) {
        del_(old_ptr); // 释放旧内存,管理新内存
    }
}

4. 底层内存管理流程(示例)

{
    unique_ptr<int> up(new int(10)); // 构造:ptr_ 指向堆内存的 10
    unique_ptr<int> up2 = std::move(up); // 移动构造:up2 接管所有权,up.ptr_ = nullptr
    up2.reset(); // 重置:调用 del_(ptr_),释放堆内存,up2.ptr_ = nullptr
} // 离开作用域,up2 析构:ptr_ 已为空,无操作;up 析构:ptr_ 为空,无操作
  • 全程无手动 delete,内存自动释放,且无额外开销。

5. 底层坑点与细节

  1. 禁止管理栈内存:unique_ptr 的删除器默认调用 delete,若管理栈内存(unique_ptr<int> up(&a)),析构时会尝试释放栈内存,导致未定义行为。
  2. 不支持数组的下标访问(非特化版本):unique_ptr<int> up(new int[5]) 是错误的,析构时会调用 delete 而非 delete[],应使用 unique_ptr<int[]> up(new int[5]),特化版本支持 [] 下标访问。
  3. 移动后源对象为空:移动后的 unique_ptr 其 ptr_ 为 nullptr,不可再解引用,否则会触发空指针异常。

二、std::shared_ptr:共享式智能指针(最常用、有额外开销)

1. 核心设计思想

shared_ptr 实现共享所有权:多个 shared_ptr 可以共享同一个原生指针,当最后一个 shared_ptr 析构时,才会释放所管理的内存。其底层核心是引用计数(Reference Count),通过引用计数来跟踪当前有多少个 shared_ptr 共享该原生指针。

2. 底层核心数据结构(简化版)

shared_ptr 也是一个模板类,但底层结构比 unique_ptr 复杂,包含两个核心指针和一个引用计数块(控制块),存在额外堆内存开销(控制块)。

(1)整体结构(模板类)
template <typename T, typename Deleter = std::default_delete<T>, typename Allocator = std::allocator<T>>
class shared_ptr {
private:
    T* ptr_; // 管理的原生指针(指向堆内存对象/数组,称为“对象指针”)
    ControlBlock* ctrl_block_; // 指向控制块的指针(称为“控制块指针”)

public:
    // 核心接口...
};
(2)控制块(Control Block):核心中的核心

控制块是 shared_ptr 底层的关键,每次创建一个新的 shared_ptr(非拷贝 / 移动)时,都会在堆上分配一个控制块,用于存储引用计数、删除器、分配器等信息。控制块的简化结构如下:

struct ControlBlock {
    size_t use_count_; // 强引用计数:当前共享该原生指针的 shared_ptr 个数
    size_t weak_count_; // 弱引用计数:当前观察该原生指针的 weak_ptr 个数
    std::default_delete<T> del_; // 删除器(存储在控制块,而非 shared_ptr 本身)
    std::allocator<T> alloc_; // 分配器(存储在控制块,用于释放控制块自身)

    // 构造函数
    ControlBlock() : use_count_(1), weak_count_(0), del_(), alloc_() {}
    ControlBlock(Deleter del, Allocator alloc) : use_count_(1), weak_count_(0), del_(del), alloc_(alloc) {}
};
关键细节拆解:
  1. 两个指针的作用:
    • ptr_:直接指向管理的对象(如 new int(10)),用于解引用(*up)、下标访问等操作。
    • ctrl_block_:指向堆上的控制块,用于访问引用计数、删除器等信息,所有共享同一个对象的 shared_ptr,其 ctrl_block_ 都指向同一个控制块。
  2. 控制块的创建时机(必须牢记,避免引用计数错误):
    • 直接用原生指针构造 shared_ptr 时(shared_ptr<int> sp(new int(10))),创建新控制块。
    • 用 std::make_shared 构造时(推荐),一次性分配 “对象内存 + 控制块内存”,效率更高(减少一次堆分配)。
    • 拷贝 / 移动 shared_ptr 时,不创建新控制块,仅复制 ctrl_block_ 指针,并递增强引用计数。
    • 用 weak_ptr 构造 shared_ptr 时,不创建新控制块,仅递增强引用计数。
  3. 引用计数的含义:
    • 强引用计数(use_count_):跟踪当前有效的 shared_ptr 个数,当 use_count_ 减至 0 时,销毁管理的对象(调用删除器),但不销毁控制块。
    • 弱引用计数(weak_count_):跟踪当前有效的 weak_ptr 个数,当 weak_count_ 减至 0 时,销毁控制块(释放控制块的堆内存)。
  4. 删除器 / 分配器存储在控制块:shared_ptr 的删除器和分配器不是模板参数的一部分(与 unique_ptr 不同),而是存储在控制块中,这使得 shared_ptr 的拷贝 / 移动更灵活,但控制块占用更多内存。
  5. 额外堆内存开销:控制块需要在堆上分配,这是 shared_ptr 相比 unique_ptr 的性能损耗点之一(还有引用计数的原子操作开销)。

3. 核心接口底层实现

(1)构造函数(两种核心场景)
场景 1:原生指针构造(创建新控制块)
explicit shared_ptr(T* ptr) : ptr_(ptr), ctrl_block_(nullptr) {
    if (ptr != nullptr) {
        // 1. 分配并构造控制块(堆上)
        using CtrlBlockAlloc = std::allocator<ControlBlock>;
        CtrlBlockAlloc alloc;
        ctrl_block_ = alloc.allocate(1);
        alloc.construct(ctrl_block_, ControlBlock()); // use_count_ = 1, weak_count_ = 0
    }
}
  • 若 ptr 非空,在堆上分配控制块,并初始化强引用计数为 1,弱引用计数为 0。
场景 2:拷贝构造(共享控制块,递增强引用计数)
shared_ptr(const shared_ptr& other) noexcept : ptr_(other.ptr_), ctrl_block_(other.ctrl_block_) {
    if (ctrl_block_ != nullptr) {
        // 关键:原子递增强引用计数(线程安全)
        std::atomic_fetch_add(&ctrl_block_->use_count_, 1);
    }
}
  • 复制 other 的 ptr_ 和 ctrl_block_,然后原子递增强引用计数(atomic_fetch_add 保证多线程环境下引用计数的正确性)。
  • 引用计数的操作必须是原子操作,这是 shared_ptr 线程安全的基础(但 shared_ptr 管理的对象本身不是线程安全的)。
场景 3:std::make_shared 构造(优化堆分配)

std::make_shared 是创建 shared_ptr 的推荐方式,底层一次性分配 “对象内存 + 控制块内存”,减少一次堆分配,效率更高:

template <typename T, typename... Args>
shared_ptr<T> make_shared(Args&&... args) {
    // 1. 一次性分配对象内存 + 控制块内存(连续堆内存)
    using Alloc = std::allocator<std::pair<ControlBlock, T>>;
    Alloc alloc;
    auto* mem = alloc.allocate(1); // 分配连续内存

    // 2. 构造控制块(use_count_ = 1)
    ControlBlock* ctrl_block = &mem->first;
    new (ctrl_block) ControlBlock();

    // 3. 构造对象(完美转发参数)
    T* ptr = &mem->second;
    new (ptr) T(std::forward<Args>(args)...);

    // 4. 构造 shared_ptr 并返回
    return shared_ptr<T>(ptr, ctrl_block);
}
  • 优势:减少一次堆分配和一次堆释放,控制块和对象在连续内存中,缓存友好性更好。
  • 劣势:对象内存和控制块内存绑定,即使 use_count_ 减至 0(对象销毁),控制块也需等待 weak_count_ 减至 0 才会释放,可能导致 “内存延迟释放”。
(2)析构函数(核心:递减强引用计数,释放对象和控制块)
~shared_ptr() noexcept {
    if (ctrl_block_ != nullptr) {
        // 1. 原子递减强引用计数
        size_t new_use_count = std::atomic_fetch_sub(&ctrl_block_->use_count_, 1);
        if (new_use_count == 1) { // 递减后 use_count_ = 0(当前是最后一个 shared_ptr)
            // 2. 销毁管理的对象(调用删除器)
            if (ptr_ != nullptr) {
                ctrl_block_->del_(ptr_);
            }
            // 3. 检查弱引用计数,若为 0,销毁控制块
            if (ctrl_block_->weak_count_ == 0) {
                // 释放控制块内存(使用控制块中的分配器)
                using CtrlBlockAlloc = std::allocator<ControlBlock>;
                CtrlBlockAlloc alloc = ctrl_block_->alloc_;
                alloc.destroy(ctrl_block_);
                alloc.deallocate(ctrl_block_, 1);
            }
        }
    }
}
  • 核心逻辑:只有当强引用计数减至 0 时,才销毁管理的对象;只有当弱引用计数也减至 0 时,才销毁控制块。
  • 原子操作保证多线程环境下引用计数的正确性,避免竞态条件。
(3)移动构造(转移所有权,无引用计数操作)
shared_ptr(shared_ptr&& other) noexcept : ptr_(other.ptr_), ctrl_block_(other.ctrl_block_) {
    // 关键:将 other 的指针置空,避免其析构时操作控制块
    other.ptr_ = nullptr;
    other.ctrl_block_ = nullptr;
}
  • 移动构造不修改引用计数,仅转移 ptr_ 和 ctrl_block_ 的所有权,效率与 unique_ptr 相当。
(4)重置(reset ())
void reset(T* new_ptr = nullptr) noexcept {
    // 1. 保存当前控制块(用于后续析构)
    ControlBlock* old_ctrl_block = ctrl_block_;
    T* old_ptr = ptr_;

    // 2. 重置当前指针
    ptr_ = new_ptr;
    ctrl_block_ = nullptr;

    // 3. 若有旧控制块,递减强引用计数(逻辑同析构函数)
    if (old_ctrl_block != nullptr) {
        size_t new_use_count = std::atomic_fetch_sub(&old_ctrl_block_->use_count_, 1);
        if (new_use_count == 1) {
            if (old_ptr != nullptr) {
                old_ctrl_block_->del_(old_ptr);
            }
            if (old_ctrl_block_->weak_count_ == 0) {
                using CtrlBlockAlloc = std::allocator<ControlBlock>;
                CtrlBlockAlloc alloc = old_ctrl_block_->alloc_;
                alloc.destroy(old_ctrl_block_);
                alloc.deallocate(old_ctrl_block_, 1);
            }
        }
    }

    // 4. 若有新指针,创建新控制块
    if (new_ptr != nullptr) {
        using CtrlBlockAlloc = std::allocator<ControlBlock>;
        CtrlBlockAlloc alloc;
        ctrl_block_ = alloc.allocate(1);
        alloc.construct(ctrl_block_, ControlBlock());
    }
}

4. 底层内存管理流程(示例)

{
    // 1. 创建 sp1,分配控制块(use_count_=1,weak_count_=0)
    shared_ptr<int> sp1(new int(10));
    // 2. 拷贝 sp1 到 sp2,共享控制块,use_count_ 原子递增为 2
    shared_ptr<int> sp2 = sp1;
    // 3. 移动 sp1 到 sp3,sp1 置空,use_count_ 仍为 2
    shared_ptr<int> sp3 = std::move(sp1);
    // 4. sp2 重置,use_count_ 原子递减为 1
    sp2.reset();
} // 离开作用域:
  // - sp3 析构:use_count_ 原子递减为 0,销毁对象(int(10)),weak_count_=0,销毁控制块
  // - sp2 析构:ctrl_block_ 已为空,无操作
  // - sp1 析构:ctrl_block_ 已为空,无操作
  • 只有最后一个 shared_ptr(sp3)析构时,才释放对象和控制块,实现共享所有权。

5. 底层坑点与细节

  1. 循环引用问题:这是 shared_ptr 最经典的底层坑,两个 shared_ptr 互相引用,会导致强引用计数无法减至 0,内存泄漏。例如:
    struct Node {
        shared_ptr<Node> next;
    };
    shared_ptr<Node> n1(new Node());
    shared_ptr<Node> n2(new Node());
    n1->next = n2;
    n2->next = n1; // 循环引用,n1 和 n2 的 use_count_ 均为 2,析构时无法减至 0
    
    • 解决方案:使用 weak_ptr 打破循环引用(见下文)。
  2. 避免多个 shared_ptr 管理同一个原生指针(非拷贝):这会创建多个独立的控制块,导致重复释放内存。例如:
    int* p = new int(10);
    shared_ptr<int> sp1(p);
    shared_ptr<int> sp2(p); // 错误:创建第二个控制块,sp1 和 sp2 析构时都会释放 p,重复释放
    
  3. 引用计数的原子操作仅保证计数本身安全:shared_ptr 的引用计数操作是线程安全的,但对管理的对象的读写操作不是线程安全的,需要手动加锁。
  4. std::make_shared 无法指定自定义删除器:若需要使用自定义删除器,只能用原生指针构造 shared_ptr。

三、std::weak_ptr:弱引用智能指针(辅助 shared_ptr,无所有权)

1. 核心设计思想

weak_ptr 是辅助 shared_ptr 的弱引用智能指针,它不拥有对象的所有权,仅能观察 shared_ptr 管理的对象。其核心作用是打破 shared_ptr 的循环引用,同时避免悬垂指针。

weak_ptr 底层也依赖 shared_ptr 的控制块,仅跟踪弱引用计数,不影响强引用计数,因此不会阻止 shared_ptr 释放对象。

2. 底层核心数据结构(简化版)

weak_ptr 也是一个模板类,底层结构比 shared_ptr 简单,仅包含一个控制块指针(无对象指针,因为不拥有所有权):

template <typename T>
class weak_ptr {
private:
    ControlBlock* ctrl_block_; // 指向 shared_ptr 的控制块(与 shared_ptr 共享同一个控制块)

public:
    // 核心接口...
};
关键细节拆解:
  1. 无对象指针(ptr_):weak_ptr 不直接管理对象,因此不需要存储对象指针,仅需存储控制块指针,用于访问弱引用计数和检查对象是否存活。
  2. 不影响强引用计数:weak_ptr 的创建、拷贝、移动都只会修改弱引用计数(weak_count_),不会修改强引用计数(use_count_),因此不会阻止 shared_ptr 释放对象。
  3. 必须从 shared_ptr 或 weak_ptr 构造:weak_ptr 不能直接用原生指针构造,只能从 shared_ptr 或其他 weak_ptr 构造,保证其控制块指针的有效性。
  4. 无解引用操作(*/->):因为 weak_ptr 不拥有对象所有权,对象可能已经被 shared_ptr 释放,因此不支持直接解引用,需要先通过 lock() 方法获取一个 shared_ptr,再解引用。

3. 核心接口底层实现

(1)构造函数(从 shared_ptr 构造)
weak_ptr(const shared_ptr<T>& sp) noexcept : ctrl_block_(sp.ctrl_block_) {
    if (ctrl_block_ != nullptr) {
        // 原子递增弱引用计数
        std::atomic_fetch_add(&ctrl_block_->weak_count_, 1);
    }
}
  • 复制 shared_ptr 的控制块指针,原子递增弱引用计数,不修改强引用计数。
(2)拷贝构造(从其他 weak_ptr 构造)
weak_ptr(const weak_ptr& other) noexcept : ctrl_block_(other.ctrl_block_) {
    if (ctrl_block_ != nullptr) {
        std::atomic_fetch_add(&ctrl_block_->weak_count_, 1);
    }
}
(3)析构函数(递减弱引用计数,销毁控制块)
~weak_ptr() noexcept {
    if (ctrl_block_ != nullptr) {
        // 1. 原子递减弱引用计数
        size_t new_weak_count = std::atomic_fetch_sub(&ctrl_block_->weak_count_, 1);
        if (new_weak_count == 1) { // 递减后 weak_count_ = 0
            // 2. 检查强引用计数,若为 0,销毁控制块(对象已被销毁)
            if (ctrl_block_->use_count_ == 0) {
                using CtrlBlockAlloc = std::allocator<ControlBlock>;
                CtrlBlockAlloc alloc = ctrl_block_->alloc_;
                alloc.destroy(ctrl_block_);
                alloc.deallocate(ctrl_block_, 1);
            }
        }
    }
}
  • 核心逻辑:仅递减弱引用计数,当弱引用计数减至 0 且强引用计数也为 0 时,销毁控制块。
  • 因为 weak_ptr 不拥有对象所有权,所以永远不会销毁管理的对象。
(4)lock() 方法(核心:获取有效的 shared_ptr)

lock() 是 weak_ptr 最常用的接口,底层逻辑是:检查对象是否存活(强引用计数 > 0),若存活,创建一个新的 shared_ptr,递增强引用计数;若不存活,返回一个空的 shared_ptr。

shared_ptr<T> lock() const noexcept {
    if (ctrl_block_ != nullptr && ctrl_block_->use_count_ > 0) {
        // 1. 原子递增强引用计数
        std::atomic_fetch_add(&ctrl_block_->use_count_, 1);
        // 2. 创建并返回 shared_ptr
        return shared_ptr<T>(ctrl_block_->ptr_, ctrl_block_);
    }
    // 3. 对象已销毁,返回空 shared_ptr
    return shared_ptr<T>();
}
  • lock() 是线程安全的,通过检查强引用计数保证获取的 shared_ptr 管理的对象是存活的。
(5)expired() 方法(检查对象是否存活)
bool expired() const noexcept {
    return (ctrl_block_ == nullptr) || (ctrl_block_->use_count_ == 0);
}
  • 若返回 true,表示对象已被销毁;若返回 false,表示对象仍存活。

4. 底层内存管理流程(打破循环引用示例)

struct Node {
    weak_ptr<Node> next; // 用 weak_ptr 替代 shared_ptr,打破循环引用
};
{
    shared_ptr<Node> n1(new Node());
    shared_ptr<Node> n2(new Node());
    n1->next = n2;
    n2->next = n1; // n1->next 是 weak_ptr,不增加 n2 的 use_count_;n2->next 是 weak_ptr,不增加 n1 的 use_count_
} // 离开作用域:
  // - n1 析构:use_count_ 减至 0,销毁 n1 对象,检查 weak_count_(n2->next 引用),递减为 0,销毁控制块
  // - n2 析构:use_count_ 减至 0,销毁 n2 对象,检查 weak_count_(n1->next 引用),递减为 0,销毁控制块
  • weak_ptr 不增加强引用计数,因此循环引用被打破,内存正常释放。

5. 底层坑点与细节

  1. weak_ptr 不能直接解引用:必须先调用 lock() 获取 shared_ptr,再解引用,否则无法保证对象存活。
  2. lock() 返回的 shared_ptr 为空时,不可解引用:若对象已被销毁,lock() 返回空 shared_ptr,解引用会触发空指针异常。
  3. weak_ptr 也存在控制块开销:weak_ptr 指向 shared_ptr 的控制块,因此也存在堆内存开销(控制块)。

四、三大智能指针底层对比(核心差异)

对比维度std::unique_ptrstd::shared_ptrstd::weak_ptr
所有权独占所有权共享所有权无所有权(弱引用)
底层核心结构栈上:对象指针 + 删除器栈上:对象指针 + 控制块指针;堆上:控制块(引用计数 + 删除器)栈上:控制块指针(共享 shared_ptr 的控制块)
引用计数无强引用计数(use_count_)+ 弱引用计数(weak_count_)仅跟踪弱引用计数(weak_count_)
额外开销无(与原生指针等价)有(控制块 + 原子操作)有(控制块)
拷贝 / 移动禁止拷贝,支持移动支持拷贝(递增强引用)+ 移动支持拷贝(递增弱引用)+ 移动
析构逻辑直接释放对象(若非空)强引用计数为 0 时释放对象,弱引用计数为 0 时释放控制块递减弱引用计数,弱引用计数为 0 且强引用计数为 0 时释放控制块
核心用途单个对象的独占管理,性能优先多个对象共享同一个资源,灵活优先打破 shared_ptr 循环引用,观察对象存活状态
循环引用无此问题存在循环引用问题,导致内存泄漏解决 shared_ptr 循环引用问题

五、核心总结

  1. 底层核心思想:三大智能指针均基于 RAII 机制,在析构时自动释放资源,避免手动 delete;差异的核心是所有权管理方式。
  2. unique_ptr 底层:独占所有权,栈上封装对象指针 + 删除器,无额外开销,禁用拷贝、支持移动,析构直接释放对象。
  3. shared_ptr 底层:共享所有权,依赖堆上控制块存储引用计数,拷贝递增强引用、析构递减强引用,最后一个释放对象,存在循环引用和额外开销问题。
  4. weak_ptr 底层:无所有权,共享 shared_ptr 的控制块,仅跟踪弱引用计数,通过 lock() 获取有效 shared_ptr,核心用于打破循环引用。
  5. 性能优先级:unique_ptr > shared_ptr > weak_ptr(weak_ptr 通常配合 shared_ptr 使用),实际开发中优先使用 unique_ptr,需要共享时使用 shared_ptr,循环引用时用 weak_ptr。
  6. 关键避坑:shared_ptr 避免循环引用、避免多个 shared_ptr 管理同一个原生指针;unique_ptr 避免管理栈内存;weak_ptr 解引用前必须 lock() 检查。
CPP 智能指针总结分享 CPP 智能指针 shared_ptr、unique_ptr、weak_ptr 总结梳理分享 阅读详情

相关推荐

避开ego_planner仿真的第一个坑:深度相机、点云与odom话题配置全指南

本文详细解析了ego_planner仿真中深度相机、点云与odom话题配置的关键问题,帮助开发者避开常见陷阱。从数据流架构到具体配置细节,包括深度相机的内外参匹配、点云数据规范要求以及odom话题的验证方法,提供了全面的实战指南和优化建议。

weixin_30335575的博客 493

C++:智能指针

本文介绍了智能指针的概念和原理,以及C++标准库中的auto_ptr、unique_ptr和shared_ptr三种智能指针,分析了shared_ptr中的循环引用问题,并由shared_ptr的循环引用问题出发点,介绍了weak_ptr。

weixin_43908419的博客 8673

小红书私信接口与Hook技术实战:微信跳转卡片的合规与破解之道

本文深入探讨了小红书私信接口与Hook技术的实战应用,重点解析了微信跳转卡片的合规使用与破解方法。通过官方私信接口的合规玩法与Xposed框架的Hook技术对比,为开发者提供了技术选型指南和实战建议,帮助实现高效的用户跳转与转化。

weixin_30399821的博客 736

C++智能指针详解

智能指针有多智能?

小白成长之路 1万+

从赛题到实战:一份面向高职“区块链技术应用”赛项的食品溯源系统全栈部署与运维指南

本文为高职院校学生备战“区块链技术应用”技能大赛提供了一份详实的食品溯源系统全栈部署与运维指南。内容涵盖从理解赛题业务逻辑、绘制技术蓝图,到搭建4节点FISCO BCOS联盟链、部署管理平台与智能合约,再到核心运维技能与前后端联调测试的完整流程,旨在帮助参赛者将赛题转化为可运行的实战项目,全面提升区块链全栈开发与运维能力。

weixin_29172201的博客 680

Win11上从零跑通Carla自动驾驶仿真:保姆级环境配置与避坑指南(Python 3.7+)

本文详细介绍了在Win11系统上从零配置Carla自动驾驶仿真环境的完整流程,包括Python 3.7+环境搭建、依赖安装、常见问题排查及性能优化技巧。通过保姆级教程帮助开发者快速避坑,实现稳定的自动驾驶仿真测试环境部署。

weixin_33488239的博客 421

【C++11】智能指针

【C++11】智能指针

weixin_60478154的博客 8318

第7次CCF PTA编程培训师资认证-T1 真题解析(2024下)

B。

lan_in的博客 513

【C++】智能指针详解

今天我们来讲一下c++11中的智能指针。

就爱摆烂 1万+

机器人中的数值优化(一)—— 数学优化、凸集合与凸函数

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考,主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等,本系列文章篇数较多,不定期更新,上半部分介绍无约束优化,下半部分介绍带约束的优化,中间会穿插一些路径规划方面的应用实例。

慕羽★的博客 2521

足式机器人全身 MPPI: 利用 MPPI 对足式机器人进行实时全身控制

本研究首次在真实四足机器人上成功部署了基于全身采样的模型预测控制(MPC)系统。采用模型预测路径积分(MPPI)算法,通过并行采样和评估控制轨迹实现实时运动规划。该系统能处理复杂接触场景,完成推箱子、爬越障碍及崎岖地形行走等任务。实验使用Unitree Go1机器人,结合MuJoCo仿真器快速生成运动策略,仅需简单控制策略即可实现稳健操作。硬件验证表明,该方法能有效完成目标导向的箱子推动任务。该成果标志着采样MPC在足式机器人实际应用中的重要突破。

2506_90492529的博客 999

[cpp进阶]C++智能指针

智能指针运用了RAII的思想。RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

weixin_53027918的博客 2024

使用MATLAB和Simulink来设计并仿真一个智能家居多传感器数据融合系统

智能家居多传感器数据融合系统旨在通过集成来自多种不同类型的传感器的数据,以提供更精确和可靠的环境信息或用户状态监测。例如,将温度、湿度、光照强度、运动检测等多种传感器的数据结合起来,可以更好地了解家庭内部的状态,并做出相应的调整或响应。在这个教程中,我们将使用MATLAB和Simulink来设计并仿真一个智能家居多传感器数据融合系统。

amy_mhd的博客 189

[cpp] 智能指针

cpp--智能指针详解

BinaryWiker的博客 1223

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

CPP智能指针

4. 如果。

m0_55988098的博客 1208

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

cpp智能指针

智能指针是c++中用于自动管理动态内存的类模板,通过RAII(资源获取即自动化)机制确保内存安全使用智能指针时要包含头文件<memory>unique_ptr独占所有权,同一时间只能有一个指针指向资源。当不希望对象的所有权被共享(即不希望多个指针同时指向同一个对象并可以修改它),unique_ptr是理想的选择。它强制了独占所有权,有助于避免因共享所有权带来的复杂的资源管理问题。用于管理单一资源,比如说局部对象、函数返回值等适合使用。

2401_88128034的博客 1244

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

cpp 智能指针

cpp 智能指针

ckg3824278的博客 1228

cpp自学 day26(智能指针)

摘要:本文详细解析C++11引入的智能指针机制,重点介绍三种核心类型:unique_ptr(独占所有权,支持移动语义)、shared_ptr(共享所有权,基于引用计数)和weak_ptr(非拥有性引用,解决循环引用问题)。智能指针通过RAII原则自动管理内存,有效防止内存泄漏和野指针。文章对比了make_unique/make_shared与传统new创建方式的优劣,提供典型使用场景示例,并列出各类型常用方法。特别强调使用注意事项,包括避免裸指针混用、循环引用风险等。通过智能指针可提升代码安全性和简洁性,是

CWNU_Dragon的博客 1306

C++ 智能指针

内存泄漏是指程序在动态分配内存后,未能正确释放这些内存,导致内存资源的浪费。随着时间的推移,未被释放的内存越来越多,最终可能导致系统资源耗尽。

azaz_plus的博客 1107

智能指针(cpp)

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。// 1.内存申请了忘记释放// 2.异常安全问题Func();// 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.

z202331720425的博客 1105

Cherno的Cpp教程笔记022:C++的智能指针

智能指针

ResumeProject的博客 341

cpp智能指针的原理与使用

智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏和空悬指针等等问题。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。智能指针的历史历程:C++ 98 中产生了第一个智能指针auto_ptr。C++boost给出了更加实用的scoped_ptr(防止拷贝) 和 shared_ptr(引进引用计数) 和 weak_ptr。

菜一头包的博客 1216

CPP——智能指针

适用于需要独占资源的场景,自动管理内存,禁止拷贝,只能移动。:适用于资源需要被多个对象共享的场景,自动管理内存,使用引用计数,线程安全。:用于解决之间的循环引用问题,提供弱引用,不增加引用计数。通过合理使用这些智能指针,可以有效避免内存泄漏和悬挂指针等常见问题,使代码更加安全和易于维护。Valgrind 是一个内存调试工具,可以检测内存泄漏、未初始化的内存读取、越界访问等问题。

huiohuoi的博客 789

[C++] 智能指针 进阶

[C++] 智能指针 进阶 在很久之前我们探讨了智能指针的浅显认识,接下来会更加深入,从源码角度认识智能指针,从而了解智能指针的设计原理,并应用到以后的工作项目中。本文将会按照C++智能指针的发展历史,回顾智能指针的各个版本的设计。后半部分将会模拟实现最常考的shared_ptr的功能。

石墨博客 1251
上一篇: 【c++】【stl】std::allocator(vector 默认分配器)
下一篇: 【c++】智能指针工厂函数
學602
博客等级 码龄2年 8粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值