C++ - 智能指针

C++ 智能指针是现代 C++ 编程中用于管理动态内存和资源的核心工具。它们基于 ‌RAII‌(Resource Acquisition Is Initialization,获取资源即初始化)惯用法,确保在对象生命周期结束时自动释放资源,从而有效防止内存泄漏并提高程序的异常安全性。

1. 核心概念与优势

  • 自动内存管理‌:智能指针是栈上分配的类模板对象,它们封装了堆上分配的原始指针。当智能指针超出作用域时,其析构函数会自动被调用,进而释放所管理的内存或资源。
  • 异常安全‌:即使代码执行过程中抛出异常,栈展开机制也会确保智能指针的析构函数被执行,从而避免资源泄漏。
  • 替代原始指针 ‌: 在现代 C++ 中,应尽量避免直接使用 newdelete。原始指针仅应用于范围极小、性能极度敏感且不涉及所有权转移的场景。

2. 主要类型

C++ 标准库(<memory> 头文件)提供了三种主要的智能指针,分别适用于不同的所有权场景:

(1) std::unique_ptr ------ 独占所有权
(2) std::shared_ptr ------ 共享所有权
(3) std::weak_ptr ------ 弱引用(观察权)
cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
    /* 1、********************    unique_ptr    ***************  */
    //独占所有权
    std::unique_ptr<int> ptr = std::make_unique<int>(12);
    std::cout << "ptr value: " << *ptr << std::endl;

    //转移所有权,此时ptr为空,ptr2拥有资源
    //ptr2接管了堆上int对象的所有权,ptr被重置为nullptr(空指针),它不再管理任何资源
    std::unique_ptr<int> ptr2 = std::move(ptr);

    //*ptr: 解引用空指针,程序崩溃
    //ptr->method(); 通过空指针调用成员函数,程序崩溃
    //可以判断状态 if(ptr) if(ptr == nullptr)
    //重新赋值 ptr = std::make_unique<int>(100);
    if ( ptr == nullptr )
    {
        std::cout << "ptr is now null" << std::endl;
    }

    if (ptr2)
    {
        std::cout << "ptr2 value: " << *ptr2 << std::endl;
    }

    /* 2、********************    shared_ptr    ***************  */
    //共享所有权
    //引用计数:内部维护一个引用计数(use_count).每增加一个指向同一对象的shared_ptr,计数加1;每销毁一个计数减1
    //自动释放:当引用计数降为0时,自动删除所管理的对象
    //线程安全:引用计数的增减是原子操作,因此多个线程可以安全地拷贝或销毁指向同一个对象的shared_ptr
    //注意:这并不意味着所管理的对象本身是线程安全的
    //适用场景:多个所有者需要共享同一资源,且无法确定哪个所有者最后销毁资源
    //推荐使用std::make_shared<T>(args...) 相比直接new,make_shared只需一次内存分配(同时分配控制块和对象)
    std::shared_ptr<int> shptr = std::make_shared<int>(22);
    std::shared_ptr<int> shptr2 = shptr;  //共享所有权,引用计数变成2

    std::cout << "use count: " << shptr.use_count() << std::endl;  //输出2
    shptr2.reset();  //ptr2 释放所有权,引用计数变为 1
    std::cout << "use count after reset: " << shptr.use_count() << std::endl; //输出1


    /* 3、********************    weak_ptr    ***************  */
    //弱引用,观察权
    //不拥有资源:weak_ptr 不会增加引用计数,因此不会影响对象的生命周期
    //临时访问:必须通过.lock()方法尝试提升为shared_ptr才能访问对象。如果对象已被销毁,lock()返回空的shared_ptr
    //适用场景:解决循环引用,当两个对象互相持有对方的shared_ptr时,引用计数永远无法归零,导致内存泄露。
    //将其中一方改为weak_ptr可打破循环
    //缓存或观察者模式:需要访问对象但不希望阻止其被销毁

    return 0;
}

3. 控制块(Control Block)

std::shared_ptr对象本身并不直接包含引用计数变量。相反、它内部通常包含两个指针(在64位系统上各占8字节)

资源指针(Resource Pointer): 指向实际管理的对象

控制块指针(Control Block Pointer):指向一个动态分配的控制块。

注意:std::shared_ptr的大小通常时原始指针的两倍(因为存了两个指针),而引用计数的操作需要通过控制块指针间接访问。

对象销毁:当强引用计数(strong_count)变为0时,控制块中存储的删除器会被调用,从而销毁实际管理的对象。

控制块销毁:只有当强引用计数和弱引用计数(weak_count)同时变为0时,控制块本身的内存才会被释放。

这就是为什么即使所有shared_ptr都销毁了,如果还存在weak_ptr,实际对象的内存虽然被释放了,但控制块的内存依然保留,以便weak_ptr能安全地查询对象是否存活。

std::shared_ptr 的引用计数操作(增加和减少)使用的是‌原子操作‌ (Atomic Operations)

这意味着多个线程可以同时拷贝或销毁指向同一对象的 shared_ptr,而不会导致数据竞争或引用计数错误。

重要限制 ‌:原子性仅保证‌引用计数本身 ‌的安全,‌不保证 ‌所指向对象的数据访问安全。如果多个线程通过 shared_ptr 读写同一个对象的内容,仍需使用互斥锁 (Mutex) 等同步机制。

相关推荐
我要升天!2 小时前
C语言连接 MySQL:libmysqlclient 获取方式详解
c语言·开发语言·数据库·mysql·adb
angushine2 小时前
Python常用方法
开发语言·前端·python
潜创微科技2 小时前
CH9245:双 Type‑C 转 PD 芯片方案,便携显示与拓展坞的理想选择
c语言·开发语言
Emberone2 小时前
深入理解 C++ STL string:从接口使用到底层模拟实现
c++·stl
【 】4232 小时前
pyhon相对导入
开发语言·python
小同志002 小时前
IoC 详解
java·开发语言
t***5442 小时前
如何在 Dev-C++ 中设置和使用 Clang 编译器
开发语言·c++
csbysj20203 小时前
Markdown 段落格式
开发语言
楼田莉子3 小时前
CMake学习:CMake语法
c++·后端·学习·软件构建