RAII 

1.1 核心定义

RAII(Resource Acquisition Is Initialization)即 “资源获取即初始化”,是一种利用对象生命周期管理程序资源(内存、文件句柄、锁等)的技术。

1.2 核心原理

  • 资源在对象构造时获取(如 new 内存、加锁);
  • 资源在对象生命周期内始终有效(由对象管理,避免误释放);
  • 资源在对象析构时自动释放(析构函数是 C++ 保证会执行的代码块,即使抛异常也会调用)。

1.3 核心优势

  1. 无需显式释放资源,避免 “忘记释放” 导致的泄漏;
  2. 解决异常安全问题:即使程序抛异常,对象析构仍会执行,资源不会泄漏;
  3. 资源管理集中化,代码更简洁、健壮。

示例:RAII 解决异常导致的内存泄漏

#include <iostream>
#include <stdexcept>
using namespace std;

// RAII思想的简单资源管理类
template<class T>
class RAIIResource {
public:
    // 构造时获取资源(new内存)
    RAIIResource(T* ptr = nullptr) : _ptr(ptr) {}
    // 析构时释放资源(delete内存)
    ~RAIIResource() {
        if (_ptr) {
            cout << "释放资源:" << _ptr << endl;
            delete _ptr;
            _ptr = nullptr;
        }
    }
private:
    T* _ptr;
};

void Func() {
    RAIIResource<int> res(new int(10)); // 构造时获取资源
    throw invalid_argument("测试异常");  // 抛异常,函数终止
    // 无需显式delete,析构会自动释放资源
}

int main() {
    try {
        Func();
    } catch (const exception& e) {
        cout << "捕获异常:" << e.what() << endl;
    }
    return 0;
}

运行结果

释放资源:0x55f8d7a7c2a0
捕获异常:测试异常

即使抛异常,资源仍被正常释放,避免内存泄漏。


unique_ptr 深度解析

 核心定位

std::unique_ptr 是 C++11 引入的独占式智能指针,核心设计目标是:

  • 保证「同一时间只有一个 unique_ptr 拥有对堆资源的所有权」;

构造与初始化方式

unique_ptr 的构造核心是「接管堆资源」,且仅支持移动语义(禁用拷贝),以下是所有常用构造 / 初始化方式,按优先级排序:

make_unique 构造(C++14 及以上)

std::make_unique 是创建 unique_ptr 的最优方式:

  • 更安全:避免裸指针多次构造导致的重复释放;
  • 更高效:减少一次内存分配(直接在堆上创建对象并绑定);
  • 语法简洁:无需手动写 new
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;

struct Date {
    int year = 2024, month = 5, day = 20;
    Date(int y, int m, int d) : year(y), month(m), day(d) {}
    ~Date() { cout << "Date析构:" << this << endl; }
};

int main() {
    // 1. 构造普通类型
    unique_ptr<int> up1 = make_unique<int>(10); 
    cout << "*up1 = " << *up1 << endl; // 10

    // 2. 构造自定义类型(带参数)
    unique_ptr<Date> up2 = make_unique<Date>(2024, 5, 20); 
    cout << up2->year << endl; // 2024

    // 3. 构造数组(自动适配数组删除器)
    unique_ptr<int[]> up3 = make_unique<int[]>(5); // 长度为5的int数组
    for (int i = 0; i < 5; ++i) {
        up3[i] = i * 2; // 支持数组下标访问
        cout << up3[i] << " "; // 0 2 4 6 8
    }

    return 0;
}

基础方式:裸指针构造(不推荐,但需了解)

直接用 new 出来的裸指针构造,需注意:禁止用同一个裸指针构造多个 unique_ptr(会导致重复释放)

int main() {
    // 1. 普通类型构造
    unique_ptr<int> up1(new int(100)); 
    // 2. 自定义类型构造
    unique_ptr<Date> up2(new Date(2025, 1, 1)); 
    // 3. 数组构造(必须用[]特化,否则析构时会用delete而非delete[])
    unique_ptr<int[]> up3(new int[3]{1,2,3}); 

    // ❌ 错误:同一裸指针构造多个unique_ptr,析构时重复释放
    // int* raw = new int(10);
    // unique_ptr<int> up4(raw);
    // unique_ptr<int> up5(raw); 

    return 0;
}

移动构造 / 赋值(转移所有权)

unique_ptr 禁用拷贝,但支持移动(move)—— 通过 std::move 转移资源所有权,原指针会被置空。

int main() {
    unique_ptr<int> up1 = make_unique<int>(10);
    cout << "up1是否为空:" << (up1 == nullptr) << endl; // 0(false)

    // 1. 移动构造
    unique_ptr<int> up2 = move(up1); 
    cout << "up1是否为空:" << (up1 == nullptr) << endl; // 1(true)
    cout << "*up2 = " << *up2 << endl; // 10

    // 2. 移动赋值
    unique_ptr<int> up3 = make_unique<int>(20);
    up3 = move(up2); // up3释放原有资源(int=20),接管up2的资源(int=10)
    cout << "*up3 = " << *up3 << endl; // 10

    return 0;
}

结果如下

up1是否为空:0
up1是否为空:1
*up2 = 10
*up3 = 10

基础方式:默认构造(空指针)

构造一个不管理任何资源的空 unique_ptr,可后续通过 reset 接管资源:

int main() {
    unique_ptr<int> up; // 默认构造,空指针
    cout << "up是否为空:" << (up == nullptr) << endl; // 1(true)

    up.reset(new int(10)); // 后续接管资源
    cout << "*up = " << *up << endl; // 10

    return 0;
}

unique_ptr 对资源的管理方式

unique_ptr 基于 RAII(资源获取即初始化) 管理资源,核心规则如下:

所有权规则(核心)

  • 独占性:一个堆资源只能被一个 unique_ptr 拥有;
  • 移动转移:所有权仅能通过 std::move 转移,转移后原 unique_ptr 变为空,不再管理任何资源;
  • 禁止拷贝:直接拷贝 unique_ptr 会编译报错(删除了拷贝构造和拷贝赋值运算符)。

释放规则

  • 析构自动释放:当 unique_ptr 生命周期结束(如出作用域、被销毁),会自动调用析构函数释放管理的资源;
  • 主动释放 / 转移:可通过 release/reset 主动控制资源的释放或转移;
  • 异常安全:即使程序抛出异常,unique_ptr 的析构函数仍会执行,确保资源不泄漏。

核心成员函数

函数 作用 用法示例
release() 释放资源所有权(返回裸指针),但不释放资源(需手动 delete) int* raw = up.release(); delete raw;
reset(ptr = nullptr) 释放当前管理的资源(若有),可选接管新资源 ptr up.reset(new int(20));(释放旧资源,接管新资源)up.reset();(仅释放旧资源)
swap(up) 交换两个 unique_ptr 的资源所有权 up1.swap(up2);
get() 获取管理的裸指针(仅观察,不释放所有权) int* raw = up.get(); cout << *raw;
get_deleter() 获取自定义删除器(仅带删除器的 unique_ptr 可用) auto del = up_file.get_deleter(); del(fp);
operator* 解引用,访问资源本体(仅非数组版可用) cout << *up;
operator-> 访问资源的成员(仅非数组版可用) cout << up->year;
operator[] 数组下标访问(仅数组版 unique_ptr<T[]> 可用) up[0] = 10;
operator bool 判断是否管理有效资源(非空) if (up) { cout << "非空"; }

unique_ptr 手动实现

#include <iostream>
#include <utility> // std::move 头文件
using namespace std;

template<class T>
class UniquePtr {
public:
    // 1. 默认构造:空指针
    UniquePtr() : _ptr(nullptr) {}

    // 2. 裸指针构造:接管堆资源(explicit避免隐式转换)
    explicit UniquePtr(T* ptr) : _ptr(ptr) {
        cout << "UniquePtr构造:接管资源 " << _ptr << endl;
    }

    // 3. 析构:自动释放资源(RAII核心)
    ~UniquePtr() {
        if (_ptr) {
            cout << "UniquePtr析构:释放资源 " << _ptr << endl;
            delete _ptr; // 数组版需重载为delete[],此处简化
            _ptr = nullptr;
        }
    }

    // 4. 移动构造:转移所有权(禁用拷贝构造)
    UniquePtr(UniquePtr&& up) noexcept : _ptr(up._ptr) {
        up._ptr = nullptr; // 原指针置空,避免二次释放
        cout << "UniquePtr移动构造:转移资源" << endl;
    }

    // 5. 移动赋值:转移所有权(禁用拷贝赋值)
    UniquePtr& operator=(UniquePtr&& up) noexcept {
        if (this != &up) { // 避免自赋值
            // 第一步:释放当前资源
            if (_ptr) {
                delete _ptr;
                cout << "UniquePtr移动赋值:释放旧资源 " << _ptr << endl;
            }
            // 第二步:转移所有权
            _ptr = up._ptr;
            up._ptr = nullptr;
            cout << "UniquePtr移动赋值:接管新资源 " << _ptr << endl;
        }
        return *this;
    }

    // 6. 禁用拷贝构造和拷贝赋值(核心:保证独占性)
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    // 7. 指针行为:重载*和->
    T& operator*() const {
        return *_ptr;
    }

    T* operator->() const {
        return _ptr;
    }


    // 9. 重载bool:判空
    explicit operator bool() const {
        return _ptr != nullptr;
    }

private:
    T* _ptr; // 管理的堆资源指针
};

// 测试代码
int main() {
    // 构造
    UniquePtr<int> up1(new int(10));
    cout << "*up1 = " << *up1 << endl;

    // 移动构造
    UniquePtr<int> up2 = move(up1);
    if (!up1) cout << "up1已为空" << endl;
    cout << "*up2 = " << *up2 << endl;

    // 移动赋值
    UniquePtr<int> up3(new int(20));
    up3 = move(up2);
    cout << "*up3 = " << *up3 << endl;
    return 0;
}

运行结果

UniquePtr构造:接管资源 0000011DCAEC9C10
*up1 = 10
UniquePtr移动构造:转移资源
up1已为空
*up2 = 10
UniquePtr构造:接管资源 0000011DCAECA410
UniquePtr移动赋值:释放旧资源 0000011DCAECA410
UniquePtr移动赋值:接管新资源 0000011DCAEC9C10
*up3 = 10
UniquePtr析构:释放资源 0000011DCAEC9C10

unique_ptr 优缺点

优点 场景说明
极致高效 无引用计数、无锁,性能和裸指针几乎一致,适合高性能场景;
绝对安全 禁用拷贝,从语法上避免浅拷贝、重复释放等经典内存错误;
异常安全 RAII 保证析构释放资源,解决「异常导致资源泄漏」问题;
用法灵活 支持自定义删除器、数组特化,可管理各种堆资源甚至非内存资源;
兼容性好 可隐式转换为 shared_ptr,满足「独占→共享」的场景切换;
语义清晰 「独占」语义明确,代码可读性高,一眼能看出资源的所有权规则;
缺点 解决方案
不支持拷贝共享 需共享资源时,改用 shared_ptr
移动后原指针失效 移动后避免使用原指针(可通过 operator bool 判空);
裸指针构造有风险 优先用 make_unique,避免同一个裸指针构造多个 unique_ptr
数组版不支持 operator*/operator-> 数组版用 operator[] 访问元素,符合数组使用习惯;
自定义删除器会增加体积 普通场景用默认删除器,特殊场景才自定义,平衡灵活性和体积;

std::shared_ptr 深度解析

std::shared_ptr 是 C++11 推出的共享式智能指针,核心设计目标是解决 unique_ptr 无法共享资源的问题 —— 通过「引用计数」跟踪资源的被引用次数,允许多个 shared_ptr 共同管理同一堆资源,仅当最后一个引用者销毁时,才释放资源。

shared_ptr 核心定位

  • 核心机制:基于 RAII + 引用计数(堆上存储计数,保证多个 shared_ptr 共享同一计数);
  • 所有权规则:多个 shared_ptr 共享资源所有权,计数≥1 时资源有效,计数 = 0 时释放资源;
  • 适用场景:需要多对象、多模块共享同一资源的场景(如容器存储指针、多线程共享对象);
  • 配套工具:需配合 weak_ptr 解决「循环引用」问题(shared_ptr 最大坑点)。

构造与初始化方式

推荐方式:make_shared 构造(C++11 及以上)

std::make_shared 是创建 shared_ptr 的最优方式:

  • 更高效:仅分配一次内存(同时存储对象和引用计数,普通构造需分配两次:对象 + 计数);
  • 更安全:避免裸指针多次构造导致的重复释放;
  • 异常安全:不会因中间步骤抛异常导致内存泄漏。
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;

struct Date {
    int year = 2024, month = 5, day = 20;
    Date(int y, int m, int d) : year(y), month(m), day(d) {
        cout << "Date构造:" << this << endl;
    }
    ~Date() {
        cout << "Date析构:" << this << endl;
    }
};

int main() {
    // 1. 构造普通类型
    shared_ptr<int> sp1 = make_shared<int>(10); 
    cout << "*sp1 = " << *sp1 << ",计数:" << sp1.use_count() << endl; // 10,计数1

    // 2. 构造自定义类型(带参数)
    shared_ptr<Date> sp2 = make_shared<Date>(2024, 5, 20); 
    cout << sp2->year << ",计数:" << sp2.use_count() << endl; // 2024,计数1

    return 0;
}

基础方式:裸指针构造

直接用 new 出来的裸指针构造,禁止用同一个裸指针构造多个 shared_ptr(会导致重复释放):

int main() {
    // 1. 普通类型构造
    shared_ptr<int> sp1(new int(100)); 
    // 2. 自定义类型构造
    shared_ptr<Date> sp2(new Date(2025, 1, 1)); 

    // ❌ 致命错误:同一裸指针构造多个shared_ptr,析构时重复释放
    // int* raw = new int(10);
    // shared_ptr<int> sp3(raw);
    // shared_ptr<int> sp4(raw); 

    return 0;
}

核心方式:拷贝构造 / 赋值(共享资源,计数 + 1)

shared_ptr 支持拷贝(区别于 unique_ptr),拷贝后多个指针共享同一资源,引用计数递增:

int main() {
    shared_ptr<int> sp1 = make_shared<int>(10);
    cout << "sp1计数:" << sp1.use_count() << endl; // 1

    // 1. 拷贝构造
    shared_ptr<int> sp2 = sp1; 
    cout << "sp1计数:" << sp1.use_count() << endl; // 2
    cout << "sp2计数:" << sp2.use_count() << endl; // 2

    // 2. 拷贝赋值
    shared_ptr<int> sp3 = make_shared<int>(20);
    sp3 = sp1; // sp3释放原有资源(int=20),共享sp1的资源,计数+1
    cout << "sp1计数:" << sp1.use_count() << endl; // 3

    return 0;
}
sp1计数:1
sp1计数:2
sp2计数:2
sp1计数:3

2.4 特殊方式:移动构造 / 赋值(转移资源,计数不变)

移动构造 / 赋值不会增加计数,而是转移原指针的资源所有权,原指针变为空:

int main() {
    shared_ptr<int> sp1 = make_shared<int>(10);
    cout << "sp1计数:" << sp1.use_count() << endl; // 1

    // 1. 移动构造
    shared_ptr<int> sp2 = move(sp1); 
    cout << "sp1是否为空:" << (sp1 == nullptr) << endl; // 1(true)
    cout << "sp2计数:" << sp2.use_count() << endl; // 1(计数不变)

    // 2. 移动赋值
    shared_ptr<int> sp3 = make_shared<int>(20);
    sp3 = move(sp2); 
    cout << "sp3计数:" << sp3.use_count() << endl; // 1

    return 0;
}

2.6 基础方式:默认构造(空指针)

构造一个不管理任何资源的空 shared_ptr,可后续通过 reset 接管资源:

int main() {
    shared_ptr<int> sp; // 默认构造,空指针
    cout << "sp计数:" << sp.use_count() << endl; // 0

    sp.reset(new int(10)); // 后续接管资源
    cout << "*sp = " << *sp << ",计数:" << sp.use_count() << endl; // 10,计数1

    return 0;
}

shared_ptr 对资源的管理方式

shared_ptr 基于「RAII + 引用计数」管理资源,核心规则如下:

引用计数的存储与更新规则

  • 计数存储:引用计数存储在堆上(而非 shared_ptr 对象内),保证多个 shared_ptr 共享同一计数;
  • 计数递增:拷贝构造 / 赋值时,计数原子性 + 1(线程安全);
  • 计数递减:析构 /reset 时,计数原子性 - 1(线程安全);
  • 释放规则:当计数减至 0 时,自动调用删除器释放管理的资源,同时释放计数本身。

线程安全规则

shared_ptr 的线程安全分为两部分:

  1. 引用计数线程安全:计数的增减操作是原子的(内部加锁),多线程拷贝 / 析构 shared_ptr 不会导致计数混乱;
  2. 资源访问线程不安全:多个线程同时修改 shared_ptr 管理的资源(如 *sp = 100),需手动加锁;
  3. shared_ptr 对象本身线程不安全:多线程同时修改同一个 shared_ptr 对象(如 sp = make_shared<int>(10)),需手动加锁。

示例:线程安全验证

#include <thread>
#include <mutex>

shared_ptr<int> sp = make_shared<int>(10);
mutex mtx; // 保护资源访问的锁

void ThreadFunc() {
    // 1. 拷贝shared_ptr:计数+1(线程安全)
    shared_ptr<int> local_sp = sp;
    cout << "线程内计数:" << local_sp.use_count() << endl;

    // 2. 修改资源:需加锁(否则数据竞争)
    lock_guard<mutex> lock(mtx);
    *local_sp += 1;
}

int main() {
    thread t1(ThreadFunc);
    thread t2(ThreadFunc);
    t1.join();
    t2.join();

    cout << "最终值:" << *sp << endl; // 12(两个线程各加1)
    return 0;
}

循环引用问题(最大坑点)

当两个 shared_ptr 互相引用时,计数无法减至 0,导致资源泄漏:

// 循环引用示例(资源泄漏)
struct Node {
    shared_ptr<Node> next; // 互相引用
    ~Node() { cout << "Node析构" << endl; } // 不会执行!
};

int main() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    n1->next = n2;
    n2->next = n1;
    // n1和n2析构时,计数各为1,无法释放资源
    return 0;
}

解决方案:用 weak_ptr 替代一方的 shared_ptrweak_ptr 不增加计数)。

核心成员函数

函数 作用 用法示例
use_count() 获取当前引用计数(仅用于调试,不保证实时性) cout << sp.use_count();
reset(ptr = nullptr) 释放当前资源所有权(计数 - 1),可选接管新资源 ptr sp.reset(new int(20));(释放旧资源,接管新资源)sp.reset();(仅释放旧资源)
swap(sp) 交换两个 shared_ptr 的资源和引用计数 sp1.swap(sp2);
get() 获取管理的裸指针(仅观察,不影响计数) int* raw = sp.get(); cout << *raw;
unique() 判断是否独占资源(计数 ==1,C++20 已废弃,改用 use_count()==1 if (sp.unique()) { cout << "独占资源"; }
operator* 解引用,访问资源本体 cout << *sp;
operator-> 访问资源的成员 cout << sp->year;
operator bool 判断是否管理有效资源(非空) if (sp) { cout << "非空"; }
get_deleter() 获取自定义删除器(仅带删除器的 shared_ptr 可用) auto del = sp_file.get_deleter(); del(fp);

核心函数实操示例

int main() {
    shared_ptr<int> sp = make_shared<int>(10);

    // 1. use_count():查看计数
    cout << "初始计数:" << sp.use_count() << endl; // 1

    // 2. reset():释放旧资源,接管新资源
    sp.reset(new int(20));
    cout << "*sp = " << *sp << ",计数:" << sp.use_count() << endl; // 20,计数1

    // 3. swap():交换资源
    shared_ptr<int> sp2 = make_shared<int>(30);
    sp.swap(sp2);
    cout << "*sp = " << *sp << endl; // 30
    cout << "*sp2 = " << *sp2 << endl; // 20

    // 4. get():获取裸指针
    int* raw = sp.get();
    cout << "*raw = " << *raw << endl; // 30

    // 5. operator bool:判空
    if (sp) {
        cout << "sp管理有效资源" << endl;
    }

    return 0;
}

shared_ptr 手动实现

#include <iostream>
#include <mutex>
#include <utility> // std::move
using namespace std;
template<class T>
class SharedPtr {
public:
    // 1. 默认构造:空指针
    SharedPtr() : _ptr(nullptr), _refCount(nullptr), _mtx(nullptr) {}

    // 2. 裸指针构造:接管资源,初始化计数为1
    explicit SharedPtr(T* ptr)
        : _ptr(ptr),
        _refCount(ptr ? new int(1) : nullptr),
        _mtx(ptr ? new mutex : nullptr) {
        cout << "SharedPtr构造:接管资源 " << _ptr << ",计数:" << use_count() << endl;
    }

    // 3. 拷贝构造:共享资源,计数+1(线程安全)
    SharedPtr(const SharedPtr& sp)
        : _ptr(sp._ptr), _refCount(sp._refCount), _mtx(sp._mtx) {
        if (_refCount) {
            // 加锁保证计数原子性
            _mtx->lock();
            ++(*_refCount);
            _mtx->unlock();
        }
        cout << "SharedPtr拷贝构造:计数=" << use_count() << endl;
    }

    // 4. 赋值重载:释放旧资源,共享新资源
    SharedPtr& operator=(const SharedPtr& sp) {
        if (this != &sp) { // 避免自赋值
            // 第一步:释放当前资源
            Release();
            // 第二步:共享新资源
            _ptr = sp._ptr;
            _refCount = sp._refCount;
            _mtx = sp._mtx;
            if (_refCount) {
                _mtx->lock();
                ++(*_refCount);
                _mtx->unlock();
            }
            cout << "SharedPtr赋值:计数=" << use_count() << endl;
        }
        return *this;
    }

    // 5. 析构:计数-1,为0时释放资源
    ~SharedPtr() {
        Release();
    }

    // 6. 指针行为:重载*和->
    T& operator*() const {
        return *_ptr;
    }

    T* operator->() const {
        return _ptr;
    }

    // 7. 核心成员函数
    int use_count() const {
        return _refCount ? *_refCount : 0;
    }

    void reset(T* ptr = nullptr) {
        Release(); // 释放当前资源
        // 接管新资源
        _ptr = ptr;
        _refCount = ptr ? new int(1) : nullptr;
        _mtx = ptr ? new mutex : nullptr;
        cout << "SharedPtr::reset():接管新资源 " << _ptr << ",计数=" << use_count() << endl;
    }

    T* get() const {
        return _ptr;
    }

    explicit operator bool() const {
        return _ptr != nullptr;
    }

private:
    // 核心:释放资源逻辑
    void Release() {
        if (_refCount) {
            _mtx->lock();
            bool needDelete = false;
            if (--(*_refCount) == 0) {
                // 计数为0,释放资源、计数、锁
                delete _ptr;
                delete _refCount;
                needDelete = true;
                cout << "SharedPtr::Release():释放资源 " << _ptr << endl;
            }
            _mtx->unlock();
            if (needDelete) delete _mtx;

            // 置空,避免野指针
            _ptr = nullptr;
            _refCount = nullptr;
            _mtx = nullptr;
        }
    }

private:
    T* _ptr;         // 管理的堆资源指针
    int* _refCount;  // 堆上的引用计数(共享计数)
    mutex* _mtx;     // 互斥锁(保证计数线程安全)
};

// 测试代码
int main() {
    // 构造
    SharedPtr<int> sp1(new int(10));
    cout << "*sp1 = " << *sp1 << endl;

    // 拷贝构造
    SharedPtr<int> sp2 = sp1;
    cout << "sp1计数:" << sp1.use_count() << endl; // 2

    // 赋值重载
    SharedPtr<int> sp3(new int(20));
    sp3 = sp1;
    cout << "sp3计数:" << sp3.use_count() << endl; // 3

    // reset
    sp3.reset(new int(30));
    cout << "*sp3 = " << *sp3 << endl; // 30

    return 0;
}

运行结果(体现核心逻辑)

SharedPtr构造:接管资源 0x55d7a9e2c2a0,计数:1
*sp1 = 10
SharedPtr拷贝构造:计数=2
sp1计数:2
SharedPtr构造:接管资源 0x55d7a9e2c2c0,计数:1
SharedPtr::Release():释放资源 0x55d7a9e2c2c0
SharedPtr赋值:计数=3
sp3计数:3
SharedPtr::Release():释放资源 0x55d7a9e2c2a0
SharedPtr::reset():接管新资源 0x55d7a9e2c2e0,计数=1
*sp3 = 30
SharedPtr::Release():释放资源 0x55d7a9e2c2e0

shared_ptr 优缺点

优点 场景说明
支持资源共享 适配多对象、多模块共享资源的场景(如容器存储指针、多线程共享对象);
引用计数线程安全 多线程拷贝 / 析构 shared_ptr 不会导致计数混乱,无需手动加锁;
异常安全 RAII 保证析构释放资源,解决「异常导致资源泄漏」问题;
用法灵活 支持自定义删除器,可管理各种堆资源甚至非内存资源;
可转换性 可接收 unique_ptr 的移动构造(unique_ptrshared_ptr);
语义清晰 「共享」语义明确,代码可读性高,一眼能看出资源的共享规则;
缺点 解决方案
性能开销 计数增减需加锁(原子操作),比 unique_ptr 慢;优先用 unique_ptr,需共享时再用 shared_ptr
循环引用 weak_ptr 替代一方的 shared_ptr(如链表节点的互相引用);
裸指针构造有风险 优先用 make_shared,避免同一个裸指针构造多个 shared_ptr
make_shared 限制 make_shared 无法自定义删除器;需自定义删除器时,用裸指针构造;
内存碎片 make_shared 一次性分配对象 + 计数,析构时计数释放但对象内存可能延迟释放(取决于内存分配器);
资源访问不安全 多线程修改资源时,手动加锁(如 std::mutex);

std::weak_ptr 

std::weak_ptr 是 C++11 推出的弱引用智能指针,它并非独立的资源管理工具,而是 std::shared_ptr 的「辅助工具」—— 核心目标是解决 shared_ptr 的循环引用问题,同时保证对资源的安全观察。

weak_ptr 核心定位

  • 核心角色shared_ptr 的「观察者」,无资源所有权,仅观察 shared_ptr 管理的资源;
  • 核心机制:不增加 shared_ptr 的引用计数,仅存储资源指针和引用计数指针,用于判断资源是否有效;
  • 核心价值:打破 shared_ptr 的循环引用(如链表 / 树节点互相引用),避免资源泄漏;
  • 使用限制:不能直接访问资源,必须通过 lock() 转为 shared_ptr 后才能安全访问;
  • 适用场景:需观察共享资源但不参与其生命周期管理的场景(如缓存、观察者模式)。

简单说:weak_ptrshared_ptr 的「旁观者」—— 只看不用,不影响资源的生老病死。

weak_ptr 构造与初始化方式

weak_ptr 必须关联 shared_ptr 才能使用,其构造 / 初始化核心是「关联已有的 shared_ptrweak_ptr」,且全程不增加引用计数

基础方式:默认构造(空 weak_ptr)

构造一个不观察任何资源的空 weak_ptr,可后续通过赋值关联 shared_ptr

#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;

struct Date {
    int year = 2024, month = 5, day = 20;
    ~Date() { cout << "Date析构:" << this << endl; }
};

int main() {
    // 默认构造:空weak_ptr,不观察任何资源
    weak_ptr<Date> wp1;
    cout << "wp1是否过期:" << wp1.expired() << endl; // 1(true,无资源可观察)
    cout << "wp1引用计数:" << wp1.use_count() << endl; // 0

    // 后续关联shared_ptr
    shared_ptr<Date> sp = make_shared<Date>();
    wp1 = sp; // 赋值关联,不增加sp的计数
    cout << "sp计数:" << sp.use_count() << endl; // 1(wp1未增加计数)
    return 0;
}

2.2 核心方式:从 shared_ptr 构造 / 赋值

这是 weak_ptr 最常用的初始化方式,直接关联 shared_ptr,但不修改其引用计数:

int main() {
    // 1. 从shared_ptr构造
    shared_ptr<int> sp1 = make_shared<int>(10);
    weak_ptr<int> wp1(sp1); // 关联sp1,不增加计数
    cout << "sp1计数:" << sp1.use_count() << endl; // 1

    // 2. 从shared_ptr赋值
    weak_ptr<int> wp2;
    wp2 = sp1; // 关联sp1,仍不增加计数
    cout << "sp1计数:" << sp1.use_count() << endl; // 1

    return 0;
}

2.3 普通方式:从 weak_ptr 拷贝 / 赋值

拷贝 / 赋值其他 weak_ptr,关联同一资源,仍不增加引用计数:

int main() {
    shared_ptr<int> sp = make_shared<int>(10);
    weak_ptr<int> wp1(sp);

    // 1. 拷贝构造
    weak_ptr<int> wp2(wp1); // 关联同一资源,计数仍为1
    cout << "sp计数:" << sp.use_count() << endl; // 1

    // 2. 拷贝赋值
    weak_ptr<int> wp3;
    wp3 = wp2; // 关联同一资源,计数仍为1
    cout << "sp计数:" << sp.use_count() << endl; // 1

    return 0;
}

2.4 特殊方式:移动构造 / 赋值

移动构造 / 赋值转移 weak_ptr 的「观察权」,原 weak_ptr 变为空,不影响引用计数:

int main() {
    shared_ptr<int> sp = make_shared<int>(10);
    weak_ptr<int> wp1(sp);

    // 1. 移动构造
    weak_ptr<int> wp2 = move(wp1);
    cout << "wp1是否过期:" << wp1.expired() << endl; // 1(空)
    cout << "wp2引用计数:" << wp2.use_count() << endl; // 1

    // 2. 移动赋值
    weak_ptr<int> wp3;
    wp3 = move(wp2);
    cout << "wp2是否过期:" << wp2.expired() << endl; // 1(空)
    cout << "wp3引用计数:" << wp3.use_count() << endl; // 1

    return 0;
}

2.5 进阶方式:空指针构造(C++17 及以上)

直接构造观察空指针的 weak_ptr,等价于默认构造:

int main() {
    weak_ptr<int> wp(nullptr); // C++17支持
    cout << "wp是否过期:" << wp.expired() << endl; // 1(true)
    return 0;
}

weak_ptr 对资源的管理方式

weak_ptr 不参与资源的生命周期管理,仅「观察」资源状态,核心规则如下:

3.1 弱引用规则

  • 无所有权weak_ptr 不拥有资源,仅存储「资源指针」和「shared_ptr 的引用计数指针」;
  • 不修改计数:构造 / 拷贝 / 赋值 weak_ptr 时,不会增减 shared_ptr 的引用计数;
  • 资源释放:资源的释放完全由 shared_ptr 的引用计数决定(计数 = 0 时释放),weak_ptr 无干预权;
  • 悬垂保护:资源释放后,weak_ptr 不会自动置空,但可通过 expired() 判断资源是否有效。

3.2 循环引用解决

shared_ptr 互相引用时,计数无法归 0 导致资源泄漏,weak_ptr 可打破这一循环:

// 循环引用问题(shared_ptr版:资源泄漏)
struct NodeBad {
    shared_ptr<NodeBad> next; // 互相引用,计数无法归0
    ~NodeBad() { cout << "NodeBad析构" << endl; } // 不会执行!
};

// 循环引用解决(weak_ptr版:正常释放)
struct NodeGood {
    weak_ptr<NodeGood> next; // 弱引用,不增加计数
    ~NodeGood() { cout << "NodeGood析构" << endl; } // 正常执行
};

int main() {
    // 1. 循环引用泄漏
    shared_ptr<NodeBad> n1 = make_shared<NodeBad>();
    shared_ptr<NodeBad> n2 = make_shared<NodeBad>();
    n1->next = n2;
    n2->next = n1;
    // n1/n2析构时,计数各为1,资源泄漏

    // 2. 循环引用解决
    shared_ptr<NodeGood> m1 = make_shared<NodeGood>();
    shared_ptr<NodeGood> m2 = make_shared<NodeGood>();
    m1->next = m2;
    m2->next = m1;
    // m1/m2析构时,计数归0,资源释放

    return 0;
}

运行结果

NodeGood析构
NodeGood析构

3.3 安全访问规则

weak_ptr 不能直接解引用(无 operator*/operator->),必须通过 lock() 转为 shared_ptr 后访问:

  • 若资源有效(计数 > 0):lock() 返回指向该资源的 shared_ptr(计数 + 1),可安全访问;
  • 若资源已释放(计数 = 0):lock() 返回空 shared_ptr,避免访问悬垂指针。

示例:安全访问资源

int main() {
    weak_ptr<int> wp;
    {
        shared_ptr<int> sp = make_shared<int>(100);
        wp = sp;
        // 资源有效时lock()
        shared_ptr<int> sp_tmp = wp.lock();
        if (sp_tmp) { // 判空后访问
            cout << "*sp_tmp = " << *sp_tmp << endl; // 100
        }
    } // sp析构,计数归0,资源释放

    // 资源已释放时lock()
    shared_ptr<int> sp_null = wp.lock();
    if (!sp_null) {
        cout << "资源已释放,无法访问" << endl;
    }

    return 0;
}

运行结果

*sp_tmp = 100
资源已释放,无法访问

3.4 线程安全规则

weak_ptr 的线程安全与 shared_ptr 一致:

  • 观察计数线程安全expired()/use_count() 读取计数是原子操作,多线程观察无问题;
  • 资源访问线程不安全lock() 得到 shared_ptr 后,修改资源仍需手动加锁;
  • weak_ptr 对象修改不安全:多线程同时修改同一个 weak_ptr(如 wp = sp),需手动加锁。

核心成员函数

函数 作用 用法示例
lock() 转为 shared_ptr:资源有效则返回非空 shared_ptr(计数 + 1),否则返回空 shared_ptr<int> sp = wp.lock(); if (sp) cout << *sp;
expired() 判断资源是否已释放(计数 ==0),返回 bool(true = 过期 / 释放) if (wp.expired()) cout << "资源已释放";
reset() 清空 weak_ptr,不再观察任何资源 wp.reset(); cout << wp.expired(); // 1
use_count() 获取关联资源的引用计数(同 shared_ptr 的计数,仅用于调试) cout << wp.use_count();
operator= 赋值关联 shared_ptr/weak_ptr,不增加计数 wp = sp; / wp = wp2;
swap(wp) 交换两个 weak_ptr 的观察目标 wp1.swap(wp2);

核心函数实操示例

int main() {
    shared_ptr<Date> sp = make_shared<Date>(2024, 5, 20);
    weak_ptr<Date> wp(sp);

    // 1. use_count():查看关联资源的计数
    cout << "关联计数:" << wp.use_count() << endl; // 1

    // 2. expired():判断资源是否有效
    cout << "资源是否过期:" << wp.expired() << endl; // 0(false)

    // 3. lock():安全访问资源
    shared_ptr<Date> sp_tmp = wp.lock();
    if (sp_tmp) {
        cout << "日期:" << sp_tmp->year << "-" << sp_tmp->month << endl; // 2024-5
    }

    // 4. reset():清空weak_ptr
    wp.reset();
    cout << "reset后是否过期:" << wp.expired() << endl; // 1(true)

    // 5. swap():交换观察目标
    weak_ptr<Date> wp2;
    wp2.swap(wp); // wp空,wp2关联原资源
    cout << "wp2是否过期:" << wp2.expired() << endl; // 0(false)

    return 0;
}

weak_ptr 手动实现

#include <iostream>
#include <mutex>
#include <utility> // std::move
using namespace std;

// 提前声明SharedPtr,便于WeakPtr访问其私有成员
template<class T>
class SharedPtr;

template<class T>
class WeakPtr {
public:
    // 1. 默认构造:空WeakPtr
    WeakPtr() : _ptr(nullptr), _refCount(nullptr), _mtx(nullptr) {}

    // 2. 从SharedPtr构造:关联资源,不增加计数
    WeakPtr(const SharedPtr<T>& sp) 
        : _ptr(sp.get()), _refCount(sp._refCount), _mtx(sp._mtx) {
        cout << "WeakPtr构造:观察资源 " << _ptr << endl;
    }

    // 3. 拷贝构造:关联同一资源,不增加计数
    WeakPtr(const WeakPtr& wp) 
        : _ptr(wp._ptr), _refCount(wp._refCount), _mtx(wp._mtx) {
        cout << "WeakPtr拷贝构造:观察资源 " << _ptr << endl;
    }

    // 4. 移动构造:转移观察权,原指针置空
    WeakPtr(WeakPtr&& wp) noexcept 
        : _ptr(wp._ptr), _refCount(wp._refCount), _mtx(wp._mtx) {
        wp._ptr = nullptr;
        wp._refCount = nullptr;
        wp._mtx = nullptr;
        cout << "WeakPtr移动构造:转移观察权" << endl;
    }

    // 5. 赋值重载:关联SharedPtr/WeakPtr
    WeakPtr& operator=(const SharedPtr<T>& sp) {
        _ptr = sp.get();
        _refCount = sp._refCount;
        _mtx = sp._mtx;
        cout << "WeakPtr赋值:观察资源 " << _ptr << endl;
        return *this;
    }

    WeakPtr& operator=(const WeakPtr& wp) {
        if (this != &wp) {
            _ptr = wp._ptr;
            _refCount = wp._refCount;
            _mtx = wp._mtx;
        }
        return *this;
    }

    // 6. 析构:仅清空指针,不修改计数(无资源需释放)
    ~WeakPtr() {
        cout << "WeakPtr析构:停止观察 " << _ptr << endl;
        _ptr = nullptr;
        _refCount = nullptr;
        _mtx = nullptr;
    }

    // 7. 核心函数:lock()转为SharedPtr
    SharedPtr<T> lock() const {
        if (!expired()) {
            // 构造SharedPtr,计数+1(安全访问资源)
            return SharedPtr<T>(_ptr, _refCount, _mtx); 
        }
        return SharedPtr<T>(); // 资源过期,返回空SharedPtr
    }

    // 8. 核心函数:expired()判断资源是否过期
    bool expired() const {
        if (!_refCount) return true;
        // 加锁保证计数读取线程安全
        _mtx->lock();
        bool isExpired = (*_refCount == 0);
        _mtx->unlock();
        return isExpired;
    }

    // 9. 核心函数:reset()清空观察
    void reset() {
        _ptr = nullptr;
        _refCount = nullptr;
        _mtx = nullptr;
    }

    // 10. 核心函数:use_count()获取计数
    int use_count() const {
        if (!_refCount) return 0;
        _mtx->lock();
        int count = *_refCount;
        _mtx->unlock();
        return count;
    }

private:
    T* _ptr;         // 观察的资源指针
    int* _refCount;  // 关联SharedPtr的引用计数(仅读取,不修改)
    mutex* _mtx;     // 关联SharedPtr的互斥锁(保证计数读取安全)
};

// 补充SharedPtr的构造函数(供WeakPtr::lock()调用)
template<class T>
class SharedPtr {
public:
    // 基础构造(之前的实现)
    explicit SharedPtr(T* ptr = nullptr) 
        : _ptr(ptr), _refCount(ptr ? new int(1) : nullptr), _mtx(ptr ? new mutex : nullptr) {
        cout << "SharedPtr构造:接管资源 " << _ptr << ",计数:" << use_count() << endl;
    }

    // 供WeakPtr::lock()调用的构造函数(共享计数,计数+1)
    SharedPtr(T* ptr, int* refCount, mutex* mtx) 
        : _ptr(ptr), _refCount(refCount), _mtx(mtx) {
        if (_refCount) {
            _mtx->lock();
            ++(*_refCount);
            _mtx->unlock();
        }
        cout << "SharedPtr(lock):共享资源 " << _ptr << ",计数:" << use_count() << endl;
    }

    // 拷贝构造、赋值重载、析构、Release()等(同之前的实现)
    SharedPtr(const SharedPtr& sp) 
        : _ptr(sp._ptr), _refCount(sp._refCount), _mtx(sp._mtx) {
        if (_refCount) {
            _mtx->lock();
            ++(*_refCount);
            _mtx->unlock();
        }
        cout << "SharedPtr拷贝构造:计数=" << use_count() << endl;
    }

    SharedPtr& operator=(const SharedPtr& sp) {
        if (this != &sp) {
            Release();
            _ptr = sp._ptr;
            _refCount = sp._refCount;
            _mtx = sp._mtx;
            if (_refCount) {
                _mtx->lock();
                ++(*_refCount);
                _mtx->unlock();
            }
            cout << "SharedPtr赋值:计数=" << use_count() << endl;
        }
        return *this;
    }

    ~SharedPtr() {
        Release();
    }

    T& operator*() const {
        if (!_ptr) throw runtime_error("空指针解引用");
        return *_ptr;
    }

    T* operator->() const {
        if (!_ptr) throw runtime_error("空指针访问成员");
        return _ptr;
    }

    int use_count() const {
        return _refCount ? *_refCount : 0;
    }

    T* get() const { return _ptr; }

    void reset(T* ptr = nullptr) {
        Release();
        _ptr = ptr;
        _refCount = ptr ? new int(1) : nullptr;
        _mtx = ptr ? new mutex : nullptr;
        cout << "SharedPtr::reset():接管新资源 " << _ptr << ",计数=" << use_count() << endl;
    }

private:
    void Release() {
        if (_refCount) {
            _mtx->lock();
            bool needDelete = false;
            if (--(*_refCount) == 0) {
                delete _ptr;
                delete _refCount;
                needDelete = true;
                cout << "SharedPtr::Release():释放资源 " << _ptr << endl;
            }
            _mtx->unlock();
            if (needDelete) delete _mtx;
            _ptr = nullptr;
            _refCount = nullptr;
            _mtx = nullptr;
        }
    }

    // 友元:允许WeakPtr访问私有成员
    friend class WeakPtr<T>;

private:
    T* _ptr;
    int* _refCount;
    mutex* _mtx;
};

// 测试代码
int main() {
    // 1. 关联SharedPtr
    SharedPtr<int> sp1(new int(10));
    WeakPtr<int> wp1(sp1);
    cout << "wp1计数:" << wp1.use_count() << endl; // 1

    // 2. lock()安全访问
    SharedPtr<int> sp2 = wp1.lock();
    cout << "*sp2 = " << *sp2 << endl; // 10
    cout << "sp1计数:" << sp1.use_count() << endl; // 2

    // 3. 资源释放后expired()
    sp1.reset();
    sp2.reset();
    cout << "wp1是否过期:" << wp1.expired() << endl; // 1(true)

    return 0;
}

运行结果

SharedPtr构造:接管资源 0x55f8d8e2c2a0,计数:1
WeakPtr构造:观察资源 0x55f8d8e2c2a0
wp1计数:1
SharedPtr(lock):共享资源 0x55f8d8e2c2a0,计数:2
*sp2 = 10
sp1计数:2
SharedPtr::Release():释放资源 0x55f8d8e2c2a0
SharedPtr::reset():接管新资源 0,计数=0
SharedPtr::Release():释放资源 0
SharedPtr::reset():接管新资源 0,计数=0
wp1是否过期:1
WeakPtr析构:停止观察 0x55f8d8e2c2a0

weak_ptr 优缺点

优点 场景说明
解决循环引用 彻底解决 shared_ptr 互相引用导致的资源泄漏(如链表、树、观察者模式);
不影响资源释放 不增加引用计数,资源释放仅由 shared_ptr 决定,符合「最小权限原则」;
安全观察资源 expired() 提前判断资源状态,lock() 避免访问悬垂指针;
轻量级 无额外性能开销(仅读取计数),比 shared_ptr 更轻量;
线程安全观察 计数读取是原子操作,多线程观察资源状态无需手动加锁;
兼容 shared_ptr 可无缝关联 shared_ptrlock() 后可复用 shared_ptr 的访问逻辑;
缺点 解决方案
不能直接访问资源 必须通过 lock() 转为 shared_ptr,访问前判空(if (sp));
依赖 shared_ptr 无法独立使用,需配合 shared_ptr,不能直接管理堆资源;
lock () 可能返回空 访问前必须判空,避免空指针解引用;
无法单独管理资源 仅作为辅助工具,不能替代 shared_ptr/unique_ptr 管理资源;
计数读取非实时 use_count() 返回的计数可能瞬间变化(仅用于调试,不建议业务逻辑依赖);

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐