1. 什么是智能指针?

智能指针:是 C++ 标准库提供的一种封装了原始指针的类模板,核心作用是自动管理动态内存,避免手动 new/delete 导致的内存泄漏(如:异常抛出时忘记释放内存)或重复释放等问题。

  • 它的本质是利用RAII(资源获取即初始化)机制:将动态内存的生命周期与智能指针对象的生命周期绑定 —— 当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放所管理的内存
2. 常用智能指针的类型有哪些?

三大常用智能指针的类型:

std::unique_ptr

  • 基本特性独占所有权,同一时间只能有一个 unique_ptr 管理某块内存,不允许拷贝,只能移动
  • 适用场景:管理单个对象或数组,明确内存归属唯一的场景

代码语言:javascript

AI代码解释

#include <memory>
using namespace std;
        
int main()
{
    //1.管理单个int对象
    unique_ptr<int> ptr1(new int(10));
    //2.或用更安全的make_unique(C++14)
    auto ptr2 = make_unique<int>(20);
        
    //3.不允许拷贝(编译报错)
    // unique_ptr<int> ptr3 = ptr1; 
        
    //4.允许移动(所有权转移)
    unique_ptr<int> ptr4 = move(ptr1);
} // 离开作用域时,ptr2、ptr4自动释放内存

std::shared_ptr

  • 基本特性共享所有权,通过引用计数记录有多少个 shared_ptr 管理同一块内存,当计数为 0 时自动释放
  • 适用场景:需要多个指针共享同一资源(如:容器中存储的对象被多个地方引用)

代码语言:javascript

AI代码解释

#include <memory>
using namespace std;
        
int main()
{
    auto ptr1 = make_shared<int>(30);
    shared_ptr<int> ptr2 = ptr1; // 引用计数变为2
        
    { //使用大括号定义了一个 “临时代码块”  ---> 定义了一个局部作用域
        shared_ptr<int> ptr3 = ptr1; // 引用计数变为3
    } // ptr3销毁,引用计数变回2
        
} // ptr1、ptr2销毁,引用计数变为0,内存释放

std::weak_ptr

  • 基本特性弱引用,不增加引用计数,用于解决 shared_ptr 的循环引用问题(两个 shared_ptr 互相引用导致计数无法归零)
  • 适用场景:作为观察者关联共享资源,不参与所有权管理

代码语言:javascript

AI代码解释

#include <memory>
using namespace std;
        
struct Node
{
    weak_ptr<Node> next; // 用weak_ptr避免循环引用
};
        
int main()
{
    auto node1 = make_shared<Node>();
    auto node2 = make_shared<Node>();
        
    node1->next = node2; // weak_ptr不增加计数
    node2->next = node1;
} // 计数正常归零,内存释放

总结:

  • 独占资源unique_ptr —> 适合 “一对一” 的场景
  • 共享资源shared_ptr —> 适合 “多对一” 的场景
  • 观察资源weak_ptr —> 解决循环引用
3. 怎么使用智能指针?

使用 C++ 智能指针的核心是利用其自动管理内存的特性,避免手动 new/delete 导致的问题。 以下是三种常用智能指针的具体使用方法和场景:


一、std::unique_ptr(独占所有权)

代码语言:javascript

AI代码解释

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

int main() 
{
    /*--------------- 创建 unique_ptr(管理单个对象)---------------*/
    //方式1:直接绑定 new 分配的内存(不推荐,存在异常安全风险)
    unique_ptr<int> ptr1(new int(10));

    //方式2:用 make_unique 创建(C++14 推荐,更安全)
    auto ptr2 = make_unique<int>(20);  // 自动推导类型

    /*--------------- 访问所管理的对象(同普通指针)---------------*/
    *ptr2 = 30;             // 修改值
    cout << *ptr2 << endl;  // 输出:30

    /*--------------- 转移所有权(只能用 move,原指针会变为空)---------------*/
    unique_ptr<int> ptr3 = move(ptr2);  //注意:ptr2 不再拥有内存
    if (ptr2 == nullptr) 
    {
        cout << "ptr2 已为空" << endl;
    }

    /*--------------- 管理动态数组(需指定数组类型)---------------*/
    unique_ptr<int[]> arr_ptr = make_unique<int[]>(5);  // 5个int的数组
    arr_ptr[0] = 1;                                     // 数组访问
}
// 离开作用域时,所有 unique_ptr 自动释放内存(无需手动 delete)

在这里插入图片描述

在这里插入图片描述

二、std::shared_ptr(共享所有权)

代码语言:javascript

AI代码解释

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

int main()
{
    //1.创建 shared_ptr(推荐用 make_shared)
    auto ptr1 = make_shared<int>(100);  // 引用计数 = 1

    //2.共享所有权(拷贝指针时,引用计数增加)
    shared_ptr<int> ptr2 = ptr1;  // 引用计数 = 2
    shared_ptr<int> ptr3 = ptr2;  // 引用计数 = 3

    //3.访问对象(同普通指针)
    *ptr3 = 200;
    cout << *ptr1 << endl;  // 输出:200(所有指针指向同一内存)

    //4.查看引用计数(use_count() 仅用于调试)
    cout << "计数:" << ptr1.use_count() << endl;  // 输出:3

    //5.局部作用域演示计数变化
    {
        shared_ptr<int> ptr4 = ptr1;  // 计数 = 4
    }  // ptr4 销毁,计数 = 3

    //6.手动释放(重置指针,计数减少)
    ptr2.reset();  // ptr2 不再指向内存,计数 = 2
}
// ptr1、ptr3 销毁,计数 = 0 → 内存自动释放

在这里插入图片描述

在这里插入图片描述

三、std::weak_ptr(弱引用,解决循环引用)

代码语言:javascript

AI代码解释

#include <iostream>
#include <memory>  // 包含智能指针所需的头文件
using namespace std;

// 定义链表节点结构
// 场景:链表节点之间可能互相引用,容易引发shared_ptr的循环引用问题
struct Node
{
    int value;               // 节点存储的值
    weak_ptr<Node> next;     // 指向链表下一个节点的弱指针
    //关键:使用weak_ptr而非shared_ptr,避免循环引用
};

int main()
{
    //1.创建两个共享指针,分别管理两个Node对象
    auto node1 = make_shared<Node>();  // node1的引用计数为1
    auto node2 = make_shared<Node>();  // node2的引用计数为1
    //注意: make_shared是创建shared_ptr的推荐方式,安全且高效
   

    //2.构建节点间的互相引用关系
    node1->next = node2;  // weak_ptr接收shared_ptr时,不增加node2的引用计数(仍为1)
    node2->next = node1;  // 同理,node1的引用计数仍为1
    //注意:若此处用shared_ptr,会导致引用计数循环增加,无法归零

    //3.1:temp是有效的shared_ptr,说明node2仍存在
    if (auto temp = node1->next.lock()) //注意:访问weak_ptr指向的对象:必须通过lock()方法转换为shared_ptr
    {
        cout << "node2 有效" << endl;
    }
    //3.2:若node2已被释放,进入此分支
    else
    {
        cout << "node2 已释放" << endl;
    }
    /* 说明:lock()的作用:检查被引用的对象是否还存在
    *     1. 若存在:返回一个指向该对象的shared_ptr(此时引用计数临时+1)
    *     2. 若已释放:返回空的shared_ptr 
    */

}  // main函数结束,局部变量node1和node2开始销毁
   // 1. node2的引用计数从1减为0 → 其管理的Node对象被释放
   // 2. node1的引用计数从1减为0 → 其管理的Node对象被释放
   // 3. 由于使用weak_ptr,没有循环引用,所有内存正常释放(无内存泄漏)

在这里插入图片描述

在这里插入图片描述

4. 为什么需要智能指针?

在 C++ 中,智能指针的出现主要是为了:解决手动管理动态内存时容易出现的问题,其核心价值在于自动管理内存生命周期,避免内存相关的 bug。


具体来说,需要智能指针的原因可以从以下几个方面理解:

1. 避免内存泄漏

手动管理动态内存(使用new分配、delete释放)时,若因逻辑疏漏导致delete未执行,会造成内存泄漏(已分配的内存无法回收,直到程序结束)

代码语言:javascript

AI代码解释

void func() 
{
    int* ptr = new int(10); // 分配内存
    
    if (someCondition) 
    {
        return; // 提前返回,导致后续的delete未执行
    }
    
    delete ptr; // 若if条件成立,此行不会执行,内存泄漏
}

智能指针会在自身生命周期结束时(如:超出作用域、被销毁)自动调用delete,无论程序执行路径如何(即使有提前返回、异常抛出等),都能保证内存被释放


2. 防止重复释放

手动释放内存时,若对同一块内存多次调用delete,会导致未定义行为(程序崩溃、数据损坏等)

代码语言:javascript

AI代码解释

void func() 
{
    int* ptr1 = new int(10);
    
    int* ptr2 = ptr; // 两个指针指向同一块内存
    
    delete ptr1;
    delete ptr2; // 重复释放,行为未定义
}

智能指针通过引用计数等机制追踪内存的引用情况,只有当最后一个引用它的智能指针被销毁时,才会真正释放内存,避免重复释放


3. 应对异常安全

当程序抛出异常时,手动管理的内存可能因delete语句被跳过而泄漏

代码语言:javascript

AI代码解释

void func() 
{
    int* ptr = new int(10);

    try 
    {
        someOperation(); // 若此函数抛出异常
    }
    catch (...) 
    {
        // 如果发生了异常:且未在catch中手动释放ptr,内存泄漏
        throw;
    }

    delete ptr;  // 如果未发生了异常:ptr指向的资源将在这里释放 
}

智能指针的析构函数会在异常发生时被自动调用(C++ 的栈展开机制),确保内存释放,无需手动在异常处理中额外处理

Logo

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

更多推荐