C++ 智能指针底层实现深度解析

C++ 智能指针是 RAII(资源获取即初始化) 设计思想的核心应用,其底层目标是自动管理动态内存(或其他资源),避免内存泄漏、野指针、重复释放等问题。C++ 标准库提供了 3 种核心智能指针(C++11 及后续标准):std::unique_ptrstd::shared_ptrstd::weak_ptr,以及已被弃用的 std::auto_ptr

一、核心设计思想:RAII(资源获取即初始化)

所有智能指针的底层核心都是 RAII,其本质是:

  • 资源绑定对象生命周期:将动态内存(或文件句柄、socket 等资源)的“申请”与智能指针对象的“构造”绑定,“释放”与智能指针对象的“析构”绑定。
  • 自动释放:当智能指针对象离开作用域(如函数返回、代码块结束、异常抛出)时,C++ 会自动调用其析构函数,完成资源释放,无需手动调用 delete

RAII 解决的核心问题:

  1. 手动 delete 遗漏导致的内存泄漏
  2. 异常抛出时,delete 未执行导致的资源泄漏
  3. 重复 delete 或释放野指针导致的未定义行为

智能指针的底层本质:封装裸指针(T),并重载 operator*operator-> 等运算符模拟指针行为,同时在析构函数中实现资源释放逻辑*。

二、std::unique_ptr 底层解析:独占所有权

std::unique_ptr独占式智能指针,核心特性是“同一时间仅一个 unique_ptr 拥有对资源的所有权”,禁止拷贝、支持移动,底层结构最简单、性能最优(接近裸指针)。

2.1 底层核心结构

unique_ptr 的底层是 “裸指针 + 可选删除器” 的紧凑结构,无额外引用计数开销,内存大小与裸指针基本一致(删除器为无状态时完全一致)。

简化版底层结构(标准库实现逻辑)
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
    T* ptr_;                  // 指向动态内存的裸指针(核心数据)
    Deleter deleter_;         // 删除器(默认是 std::default_delete<T>)

public:
    // 1. 构造函数:绑定裸指针(资源申请)
    explicit unique_ptr(T* p = nullptr) noexcept : ptr_(p) {}

    // 2. 析构函数:自动释放资源(核心)
    ~unique_ptr() noexcept {
        if (ptr_ != nullptr) {
            deleter_(ptr_);   // 调用删除器释放资源
        }
    }

    // 3. 禁止拷贝:删除拷贝构造和拷贝赋值运算符(C++11 语法)
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

    // 4. 支持移动:移动构造和移动赋值(转移所有权)
    unique_ptr(unique_ptr&& other) noexcept 
        : ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
        other.ptr_ = nullptr;  // 原指针置空,避免重复释放
    }

    unique_ptr& operator=(unique_ptr&& other) noexcept {
        if (this != &other) {
            // 先释放当前资源
            if (ptr_ != nullptr) {
                deleter_(ptr_);
            }
            // 转移所有权
            ptr_ = other.ptr_;
            deleter_ = std::move(other.deleter_);
            other.ptr_ = nullptr;
        }
        return *this;
    }

    // 5. 模拟指针行为:重载 operator* 和 operator->
    T& operator*() const noexcept { return *ptr_; }
    T* operator->() const noexcept { return ptr_; }

    // 6. 其他核心接口
    T* get() const noexcept { return ptr_; }  // 获取裸指针(谨慎使用)
    void reset(T* p = nullptr) noexcept {     // 重置指针(释放旧资源,绑定新资源)
        if (ptr_ != nullptr) {
            deleter_(ptr_);
        }
        ptr_ = p;
    }
    T* release() noexcept {                   // 释放所有权(返回裸指针,自身置空)
        T* temp = ptr_;
        ptr_ = nullptr;
        return temp;
    }
};
关键底层细节
  1. 裸指针 ptr_:直接指向动态分配的对象(如 new T 分配的内存),是 unique_ptr 唯一的核心数据,无额外内存开销。
  2. 删除器 deleter_
    • 默认是 std::default_delete<T>,其底层实现为 delete ptr(普通指针)或 delete[] ptr(数组指针)。
    • 支持自定义删除器(如释放数组、文件句柄、第三方库资源),删除器的类型是模板参数,编译时确定,无类型擦除开销。

2.2 所有权模型:独占与移动

unique_ptr 的核心是 “独占所有权”,底层通过以下机制保证:

  • 禁止拷贝:显式删除拷贝构造和拷贝赋值运算符(= delete),编译时直接报错,避免多个 unique_ptr 指向同一资源。
  • 支持移动:通过移动构造/赋值转移所有权,底层逻辑是“拷贝原指针 → 原指针置空”,确保仅有一个 unique_ptr 持有有效指针。
移动语义底层示例
std::unique_ptr<int> p1(new int(10));  // p1.ptr_ = 0x1234(假设地址)
std::unique_ptr<int> p2 = std::move(p1);  // 移动构造
// 底层状态:p2.ptr_ = 0x1234,p1.ptr_ = nullptr
p1.reset();  // 无操作(ptr_ 已为空)
p2.reset();  // 调用 delete 0x1234,释放资源

2.3 数组特化版本(unique_ptr<T[]>

unique_ptr 提供数组特化版本,底层通过模板特化区分普通指针和数组指针,核心差异在删除器:

// 数组特化版本:默认删除器为 std::default_delete<T[]>(调用 delete[])
template <typename T, typename Deleter>
class unique_ptr<T[], Deleter> {
private:
    T* ptr_;
    Deleter deleter_;
public:
    ~unique_ptr() noexcept {
        if (ptr_ != nullptr) {
            deleter_(ptr_);  // 数组版本:delete[] ptr_
        }
    }
    T& operator[](size_t idx) const noexcept { return ptr_[idx]; }  // 重载 [] 运算符
};

// 使用示例
std::unique_ptr<int[]> p(new int[5]);  // 数组版本,析构时 delete[]
p[0] = 10;  // 支持 [] 访问

2.4 底层性能开销

unique_ptr零额外开销(Zero Overhead) 设计:

  • 内存开销:与裸指针完全一致(当删除器是无状态时,如默认删除器、无捕获 lambda);若删除器有状态(如捕获变量的 lambda),则内存开销增加删除器的状态大小。
  • 时间开销:构造、析构、移动操作均为 O(1),无原子操作或复杂逻辑,性能接近裸指针。

2.5 自定义删除器底层实现

自定义删除器的本质是“提供一个可调用对象(函数指针、lambda、函数对象),在析构时调用该对象释放资源”。底层分为两种场景:

场景 1:无状态删除器(无额外开销)
// 函数对象删除器(无状态)
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp != nullptr) {
            fclose(fp);  // 释放文件句柄(而非 delete)
        }
    }
};

// 底层:FileDeleter 无成员变量,unique_ptr 内存大小 = sizeof(FILE*)
std::unique_ptr<FILE, FileDeleter> file_ptr(fopen("test.txt", "r"));
场景 2:有状态删除器(增加内存开销)
// 捕获变量的 lambda 作为删除器(有状态)
int log_level = 1;
auto deleter = [log_level](int* p) {
    if (log_level > 0) {
        std::cout << "释放内存:" << p << std::endl;
    }
    delete p;
};

// 底层:unique_ptr 内存大小 = sizeof(int*) + 捕获变量大小(此处为 sizeof(int))
std::unique_ptr<int, decltype(deleter)> p(new int(10), deleter);

三、std::shared_ptr 底层解析:共享所有权

std::shared_ptr共享式智能指针,核心特性是“多个 shared_ptr 可共享同一资源的所有权”,底层通过 引用计数(Reference Counting) 管理资源生命周期,支持拷贝和移动。

3.1 底层核心结构:双指针 + 控制块

shared_ptr 的底层并非简单封装裸指针,而是由 “数据指针(data pointer)+ 控制块指针(control block pointer)” 组成,核心复杂度集中在控制块。

1. 双指针设计
指针类型作用
数据指针(T*)指向实际动态分配的对象(如 new T 生成的对象),用于访问对象。
控制块指针(CB*)指向一个独立分配的“控制块”(Control Block),存储引用计数、删除器、分配器等元数据。
2. 控制块(Control Block)底层结构

控制块是 shared_ptr 的核心,所有共享同一资源的 shared_ptr 都会指向同一个控制块,底层结构如下(简化版):

struct ControlBlock {
    std::atomic<size_t> shared_count;  // 强引用计数:当前共享资源的 shared_ptr 数量
    std::atomic<size_t> weak_count;    // 弱引用计数:当前指向该控制块的 weak_ptr 数量
    std::function<void(void*)> deleter; // 删除器(类型擦除,支持任意可调用对象)
    std::function<void(void*)> allocator; // 分配器(用于释放控制块自身)

    // 构造函数:初始化引用计数和删除器
    ControlBlock(void* ptr, decltype(deleter) d, decltype(allocator) a)
        : shared_count(1), weak_count(0), deleter(d), allocator(a) {}

    // 析构控制块(仅当 shared_count 和 weak_count 均为 0 时调用)
    ~ControlBlock() {
        allocator(this);  // 释放控制块自身内存
    }
};
3. shared_ptr 简化版底层实现
template <typename T>
class shared_ptr {
private:
    T* data_ptr_;          // 数据指针:指向实际对象
    ControlBlock* cb_ptr_; // 控制块指针:指向共享控制块

public:
    // 1. 构造函数(从裸指针创建,生成控制块)
    explicit shared_ptr(T* p = nullptr) {
        if (p != nullptr) {
            // 分配控制块,初始化强引用计数=1,删除器=默认 delete
            cb_ptr_ = new ControlBlock(
                p,
                [](void* ptr) { delete static_cast<T*>(ptr); },  // 默认删除器
                [](void* cb) { delete static_cast<ControlBlock*>(cb); }  // 控制块分配器
            );
            data_ptr_ = p;
        } else {
            data_ptr_ = nullptr;
            cb_ptr_ = nullptr;
        }
    }

    // 2. 拷贝构造函数(共享所有权,强引用计数+1)
    shared_ptr(const shared_ptr& other) noexcept {
        if (other.cb_ptr_ != nullptr) {
            data_ptr_ = other.data_ptr_;
            cb_ptr_ = other.cb_ptr_;
            // 原子操作:强引用计数+1(保证线程安全)
            cb_ptr_->shared_count.fetch_add(1, std::memory_order_relaxed);
        } else {
            data_ptr_ = nullptr;
            cb_ptr_ = nullptr;
        }
    }

    // 3. 移动构造函数(转移所有权,无引用计数操作)
    shared_ptr(shared_ptr&& other) noexcept {
        data_ptr_ = other.data_ptr_;
        cb_ptr_ = other.cb_ptr_;
        // 原对象置空,避免析构时操作控制块
        other.data_ptr_ = nullptr;
        other.cb_ptr_ = nullptr;
    }

    // 4. 析构函数(核心:强引用计数-1,为0则释放资源)
    ~shared_ptr() noexcept {
        if (cb_ptr_ != nullptr) {
            // 原子操作:强引用计数-1
            size_t new_count = cb_ptr_->shared_count.fetch_sub(1, std::memory_order_acq_rel);
            if (new_count == 1) {
                // 强引用计数为0,释放实际对象
                cb_ptr_->deleter(data_ptr_);
                // 检查弱引用计数,若为0则释放控制块
                if (cb_ptr_->weak_count == 0) {
                    delete cb_ptr_;
                }
            }
        }
    }

    // 5. 重载 operator* 和 operator->(模拟指针行为)
    T& operator*() const noexcept { return *data_ptr_; }
    T* operator->() const noexcept { return data_ptr_; }

    // 6. 核心接口
    size_t use_count() const noexcept {  // 获取强引用计数
        return (cb_ptr_ != nullptr) ? cb_ptr_->shared_count.load() : 0;
    }
    bool unique() const noexcept {  // 是否唯一拥有所有权
        return use_count() == 1;
    }
    T* get() const noexcept { return data_ptr_; }
};

3.2 核心机制:引用计数的原子操作

shared_ptr 的引用计数(shared_countweak_count)是 原子变量(std::atomic<size_t>,底层通过原子操作保证多线程环境下的安全性,避免竞争条件。

引用计数操作的线程安全语义
  • 强引用计数增减:使用 std::memory_order_acq_rel 内存序(获取-释放语义),确保对引用计数的修改在多线程间可见,避免重排序导致的未定义行为。
  • 弱引用计数增减:同样使用原子操作,确保 weak_ptr 的操作不干扰 shared_ptr 的资源管理。
引用计数生命周期流程(示例)
// 1. 创建 shared_ptr,控制块 shared_count=1,weak_count=0
std::shared_ptr<int> p1(new int(10));

// 2. 拷贝 p1,shared_count 原子+1 → 2
std::shared_ptr<int> p2 = p1;

// 3. 移动 p2,shared_count 不变(仍为2),p2 置空
std::shared_ptr<int> p3 = std::move(p2);

// 4. p3 析构,shared_count 原子-1 → 1
p3.reset();

// 5. p1 析构,shared_count 原子-1 → 0 → 释放对象(delete int(10))
// 此时 weak_count=0 → 释放控制块

3.3 控制块的创建时机

控制块的创建是 shared_ptr 底层的关键细节,确保所有共享资源的 shared_ptr 都指向同一个控制块,避免“多个控制块管理同一资源”导致的重复释放。控制块仅在以下 3 种场景创建:

  1. 通过裸指针构造 shared_ptr(如 shared_ptr<T> p(new T)):直接创建控制块,绑定裸指针和默认删除器。
  2. 通过 std::make_shared<T> 创建(推荐):make_shared 会一次性分配“对象 + 控制块”的连续内存,效率更高(减少一次内存分配),且控制块与对象生命周期绑定。
  3. 通过 unique_ptr 移动构造 shared_ptr:从 unique_ptr 的删除器和裸指针创建控制块,转移所有权。
关键禁忌:避免用同一裸指针创建多个 shared_ptr
int* raw_ptr = new int(10);
std::shared_ptr<int> p1(raw_ptr);  // 创建控制块 A,shared_count=1
std::shared_ptr<int> p2(raw_ptr);  // 创建控制块 B,shared_count=1
// 析构时:p1 释放 raw_ptr → p2 释放已释放的裸指针 → 未定义行为(双重释放)

底层原因:两个 shared_ptr 各自创建独立控制块,引用计数独立,导致双重释放。

3.4 类型擦除:删除器的灵活实现

shared_ptr 支持任意类型的删除器(函数指针、lambda、函数对象),即使删除器类型不同,shared_ptr 的类型仍可一致(如 shared_ptr<FILE> 可搭配不同的文件释放逻辑)。底层实现依赖 类型擦除(Type Erasure)

  • 删除器存储为 std::function<void(void*)>,将删除器的类型信息“擦除”,仅保留调用接口。
  • 类型擦除的代价:std::function 有少量内存开销(通常是 3 个指针大小),且调用时有轻微的间接跳转开销(远小于内存泄漏风险)。
自定义删除器示例(底层类型擦除)
// 函数指针删除器
void free_int(int* p) { delete p; }
std::shared_ptr<int> p1(new int(10), free_int);  // 删除器类型擦除为 function

// lambda 删除器(捕获变量)
auto deleter = [](FILE* fp) { fclose(fp); };
std::shared_ptr<FILE> p2(fopen("test.txt", "r"), deleter);  // 同样类型擦除

3.5 底层性能开销

shared_ptr 的开销高于 unique_ptr,主要来自 3 方面:

  1. 内存开销

    • 双指针(data_ptr_ + cb_ptr_):通常是 2 * sizeof(void*)(64 位系统下 16 字节)。
    • 控制块:包含两个原子变量 + 两个 std::function,内存开销约 40~64 字节(取决于系统和编译器)。
    • 注:std::make_shared 可减少一次内存分配,且对象与控制块连续存储,缓存局部性更好,性能更优。
  2. 时间开销

    • 拷贝/赋值:原子操作(fetch_add/fetch_sub)的开销(约为普通整数操作的 10~100 倍,但仍是纳秒级)。
    • 析构:原子减 1 + 条件判断(释放对象/控制块),开销轻微。

3.6 线程安全特性

shared_ptr 的线程安全分为两层,底层实现决定了其安全边界:

  1. 引用计数的线程安全:强引用计数和弱引用计数的原子操作保证了“多线程同时拷贝、赋值、析构 shared_ptr”是安全的,不会导致引用计数错乱。
  2. 对象的线程安全shared_ptr 不保证所指向对象的线程安全!多线程同时访问对象的成员(如 p->set_val(5)),需手动加锁(如 std::mutex),否则会导致数据竞争。
线程安全示例与禁忌
std::shared_ptr<int> p = std::make_shared<int>(10);

// 安全:多线程同时拷贝 p(仅操作引用计数,原子操作)
std::thread t1([p]() { auto q = p; });
std::thread t2([p]() { auto q = p; });

// 不安全:多线程同时修改对象内容(无同步)
std::thread t3([p]() { *p = 20; });
std::thread t4([p]() { *p = 30; });  // 数据竞争,未定义行为

四、std::weak_ptr 底层解析:弱引用观察者

std::weak_ptr弱引用智能指针,核心特性是“不拥有资源所有权,仅观察 shared_ptr 管理的资源”,底层用于解决 shared_ptr循环引用 问题。

4.1 底层核心结构

weak_ptr 的底层结构比 shared_ptr 更简单:仅存储控制块指针(ControlBlock),无数据指针*。因为 weak_ptr 不直接访问对象,仅通过控制块观察资源状态。

简化版底层实现
template <typename T>
class weak_ptr {
private:
    ControlBlock* cb_ptr_;  // 仅指向控制块,无数据指针

public:
    // 1. 从 shared_ptr 构造(弱引用计数+1)
    weak_ptr(const shared_ptr<T>& other) noexcept {
        if (other.cb_ptr_ != nullptr) {
            cb_ptr_ = other.cb_ptr_;
            // 原子操作:弱引用计数+1
            cb_ptr_->weak_count.fetch_add(1, std::memory_order_relaxed);
        } else {
            cb_ptr_ = nullptr;
        }
    }

    // 2. 析构函数(弱引用计数-1,为0且强引用为0则释放控制块)
    ~weak_ptr() noexcept {
        if (cb_ptr_ != nullptr) {
            size_t new_weak_count = cb_ptr_->weak_count.fetch_sub(1, std::memory_order_acq_rel);
            // 仅当强引用计数为0且弱引用计数为0时,释放控制块
            if (new_weak_count == 1 && cb_ptr_->shared_count == 0) {
                delete cb_ptr_;
            }
        }
    }

    // 3. 核心接口:lock() → 升级为 shared_ptr(关键)
    shared_ptr<T> lock() const noexcept {
        if (cb_ptr_ == nullptr) {
            return shared_ptr<T>();
        }
        // 原子操作:检查强引用计数是否 > 0(资源是否存活)
        if (cb_ptr_->shared_count.load(std::memory_order_acquire) == 0) {
            return shared_ptr<T>();  // 资源已释放,返回空 shared_ptr
        }
        // 强引用计数+1,返回有效的 shared_ptr
        return shared_ptr<T>(data_ptr_from_cb(), cb_ptr_);  // 内部构造,避免创建新控制块
    }

    // 4. 其他接口
    bool expired() const noexcept {  // 检查资源是否已释放(强引用计数为0)
        return (cb_ptr_ == nullptr) || (cb_ptr_->shared_count.load() == 0);
    }
    size_t use_count() const noexcept {  // 获取当前强引用计数
        return (cb_ptr_ != nullptr) ? cb_ptr_->shared_count.load() : 0;
    }
};

4.2 核心机制:弱引用计数与资源观察

weak_ptr 的底层核心是 “弱引用计数”,其生命周期与控制块绑定,而非资源本身:

  1. weak_ptr 拷贝/构造时,仅增加 弱引用计数weak_count),不影响强引用计数(shared_count)。
  2. weak_ptr 不直接访问资源,必须通过 lock() 升级为 shared_ptr
    • 若资源存活(shared_count > 0):lock() 会原子性增加 shared_count,返回有效 shared_ptr
    • 若资源已释放(shared_count == 0):lock() 返回空 shared_ptr,避免野指针访问。

4.3 解决循环引用的底层逻辑

shared_ptr 的循环引用会导致强引用计数无法减为 0,资源泄漏。weak_ptr 因不增加强引用计数,可打破循环。

循环引用示例(无 weak_ptr)
struct A;
struct B {
    std::shared_ptr<A> ptr;  // B 持有 A 的强引用
};
struct A {
    std::shared_ptr<B> ptr;  // A 持有 B 的强引用
};

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->ptr = b;  // A 的强引用计数=1 → 2
    b->ptr = a;  // B 的强引用计数=1 → 2

    // 析构时:a 和 b 的强引用计数均减为1,无法继续减为0 → 资源泄漏
}
底层原因:强引用闭环导致 shared_count 无法归零。
用 weak_ptr 打破循环(底层逻辑)
struct A;
struct B {
    std::weak_ptr<A> ptr;  // B 持有 A 的弱引用(不增加 shared_count)
};
struct A {
    std::shared_ptr<B> ptr;  // A 持有 B 的强引用
};

int main() {
    auto a = std::make_shared<A>();  // A.shared_count=1
    auto b = std::make_shared<B>();  // B.shared_count=1
    a->ptr = b;  // B.shared_count=2
    b->ptr = a;  // A.shared_count 仍为1(weak_ptr 不增加)

    // 析构流程:
    // 1. a 析构 → A.shared_count=0 → 释放 A 对象 → A.ptr(b)的 shared_count=1
    // 2. b 析构 → B.shared_count=0 → 释放 B 对象 → B.ptr(weak_ptr)析构
    // 3. B.ptr 的 weak_count 减为0,且 A 的 shared_count=0 → 释放控制块
    // 资源完全释放,无泄漏
}

4.4 底层性能开销与线程安全

  1. 内存开销:仅一个控制块指针(cb_ptr_),大小为 sizeof(void*)(64 位系统 8 字节),无额外开销。
  2. 时间开销
    • 拷贝/析构:仅原子操作修改弱引用计数,开销与 shared_ptr 的引用计数操作相当。
    • lock():原子检查强引用计数 + 可能的强引用计数增加,开销轻微。
  3. 线程安全:与 shared_ptr 一致,弱引用计数的操作是线程安全的,但通过 lock() 获得的 shared_ptr 所指向的对象仍需手动同步。

五、已弃用的 std::auto_ptr 底层缺陷

std::auto_ptr 是 C++98 引入的早期智能指针,因底层设计缺陷被 C++11 弃用,核心缺陷在于 “拷贝语义不合理”

底层结构与拷贝逻辑

auto_ptr 的底层结构与 unique_ptr 类似(裸指针 + 默认删除器),但拷贝构造/赋值的底层逻辑是“转移所有权”,而非禁止拷贝:

template <typename T>
class auto_ptr {
private:
    T* ptr_;
public:
    // 拷贝构造函数:转移所有权(原对象置空)
    auto_ptr(auto_ptr& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;  // 原对象指针置空
    }

    // 拷贝赋值运算符:转移所有权(原对象释放资源,原对象置空)
    auto_ptr& operator=(auto_ptr& other) noexcept {
        if (this != &other) {
            delete ptr_;        // 释放当前资源
            ptr_ = other.ptr_;  // 转移所有权
            other.ptr_ = nullptr;
        }
        return *this;
    }
};

底层缺陷导致的问题

  1. 拷贝后原对象悬空
    std::auto_ptr<int> p1(new int(10));
    std::auto_ptr<int> p2 = p1;  // p1.ptr_ = nullptr,p2.ptr_ = 0x1234
    *p1 = 20;  // 访问空指针 → 未定义行为
    
  2. 无法放入容器:容器要求元素支持拷贝,auto_ptr 的拷贝会转移所有权,导致容器中元素悬空(如 vector<auto_ptr<int>> 扩容时拷贝元素,原元素置空)。
  3. 数组支持缺陷auto_ptr 无数组特化版本,析构时调用 delete 而非 delete[],释放数组会导致内存泄漏。

unique_ptr 通过“禁止拷贝、支持移动”的底层设计,完美解决了 auto_ptr 的缺陷。

六、四种智能指针底层对比总结

特性unique_ptrshared_ptrweak_ptrauto_ptr(已弃用)
底层结构裸指针 + 模板删除器(无类型擦除)双指针(数据+控制块)+ 控制块(引用计数+类型擦除删除器)控制块指针(无数据指针)裸指针 + 默认删除器
所有权模型独占所有权(不可拷贝,可移动)共享所有权(可拷贝,可移动)无所有权(仅观察)独占所有权(拷贝转移,设计缺陷)
引用计数强引用计数(原子)+ 弱引用计数(原子)仅访问控制块的引用计数
内存开销裸指针大小(删除器无状态时)双指针 + 控制块(较大)单指针(最小)裸指针大小
时间开销零额外开销(接近裸指针)原子操作 + 类型擦除(轻微开销)原子操作(轻微开销)零额外开销
线程安全无(仅对象本身线程安全,无共享状态)引用计数线程安全,对象需手动同步弱引用计数线程安全,对象需手动同步
核心场景独占资源、局部变量、容器元素共享资源、长生命周期对象打破循环引用、观察共享资源无(已弃用)
自定义删除器模板参数(编译时确定,无开销)类型擦除(std::function,轻微开销)不支持(依赖 shared_ptr 的删除器)不支持

七、核心使用原则与底层注意事项

  1. 优先使用 unique_ptr:无额外开销,独占所有权,适合大多数场景(如函数返回动态对象、容器元素)。
  2. shared_ptr 仅用于共享所有权:避免滥用(如无需共享时用 shared_ptr 会增加不必要的开销和循环引用风险)。
  3. make_shared 优先于 new 构造 shared_ptr:一次性分配对象和控制块,减少内存分配次数,提升缓存局部性。
  4. 避免用裸指针初始化多个 shared_ptr:会创建多个控制块,导致双重释放。
  5. weak_ptr 仅用于打破 shared_ptr 循环引用:不单独使用(无法直接访问对象),需通过 lock() 升级为 shared_ptr
  6. 智能指针不管理栈内存:智能指针的析构函数会调用 delete,若绑定栈内存地址,会导致释放栈内存的未定义行为。
  7. 避免 get() 返回的裸指针被其他智能指针管理get() 仅用于临时访问,若将其交给其他智能指针,会导致重复释放。

八、总结

C++ 智能指针的底层设计围绕 “RAII 思想 + 所有权管理” 展开:

  • unique_ptr 以“独占所有权 + 移动语义”为核心,追求零额外开销,是默认首选。
  • shared_ptr 以“共享所有权 + 原子引用计数”为核心,支持拷贝,适合资源共享场景,代价是轻微的内存和时间开销。
  • weak_ptr 以“弱引用 + 控制块观察”为核心,解决 shared_ptr 的循环引用问题,无额外所有权负担。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值