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) 等同步机制。

相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
BadBadBad__AK2 天前
线段树维护区间 k 次方和
c++·数学·算法·stl
卷无止境2 天前
Eigen 库如何借助 OpenMP 加速计算
c++·后端
卷无止境2 天前
OpenMPI、MPICH 与 OpenMP:关系、核心概念与架构全解
c++·后端
郝学胜_神的一滴3 天前
CMake 30:循环语法全解|foreach_while双循环精讲、迭代技巧与实战避坑指南
c++·cmake
卷无止境5 天前
C++ 的Eigen 库全解析
c++
卷无止境5 天前
现代 C++特性大盘点:一门脱胎换骨的老语言
c++·后端
郝学胜_神的一滴5 天前
CMake 27:缓存变量的特性、语法、类型与实操全解
c++·cmake
博客18007 天前
酷宝的使用方法,超好用的免费界面库,C++、MFC可用
c++·mfc·界面库·库来帮·酷宝
郝学胜_神的一滴7 天前
CMake 026:属性体系精讲、四大作用域全解 & 实战代码落地
c++·cmake