概要

在C++开发的过程中,动态内存的管理是通过一对运算符:newdelete来完成的。一位优秀的C++开发者对于动态内存的管理是非常熟练的。考虑这样一个场景,当你在使用C++进行开发并想要申请内存来进行使用时,如果你写出了一个未释放你申请的内存的程序,那么会发生什么,我们知道计算机中的内存是有限的,不论你的计算机的内存多大,如果你不断地从中申请内存,那么总有一天,你的计算机的内存会耗尽。就如同一个一直往一个水池中放水一样,总有一天水池里的水会溢出。而程序员就如同一个“水池管理员”一样在放水进来的同时也要将水放出。由此看来,这样的工作有点辛苦,每次申请内存就要记得释放。动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的。有时我们会忘记释放内存,这样就会导致内存泄漏,有时又在有指针引用内存的时候我们就释放了它,在这种情况下会产生非法引用的指针。因此,使用动态内存是非常麻烦的。但是,C++给我们提供了一个“法宝”,那就是今天我们将要讲的智能指针。

一、为什么需要智能指针?

先看一段典型的 “危险代码”—— 手动管理动态内存:

#include <iostream>
using namespace std;

void riskyFunc() {
    int* ptr = new int(10); // 手动分配内存
    // 业务逻辑(可能抛出异常、提前return)
    if (true) {
        return; // 提前返回,delete未执行
    }
    delete ptr; // 手动释放内存(可能执行不到)
}

int main() {
    riskyFunc(); // 内存泄漏!
    return 0;
}

这段代码中,return语句导致delete未执行,直接造成内存泄漏。而智能指针的核心价值就是:将动态内存绑定到对象生命周期,析构时自动释放,无需手动调用 delete

二、智能指针的核心基础:RAII 机制

智能指针的底层是 RAII(Resource Acquisition Is Initialization,资源获取即初始化)范式,其核心逻辑可总结为:

  1. 构造获取资源:智能指针对象创建时,接管动态内存(或其他资源)的所有权;
  2. 析构释放资源:当智能指针对象离开作用域(或被销毁)时,析构函数自动释放托管的资源;
  3. 全程无需手动干预:开发者只需关注资源的 “获取”,释放由编译器自动完成。

简单来说,RAII 就是让 C++ 的 “对象析构” 帮你干 “释放内存” 的活,从语法层面杜绝遗忘释放的问题。

三、C++ 三大智能指针:核心用法与场景

C++ 标准库(<memory>头文件)提供了三种核心智能指针,各自适配不同的内存管理场景,下面逐一讲解。

1. std::unique_ptr:独占式内存管理

unique_ptr独占所有权的智能指针,同一时间只能有一个unique_ptr指向同一个对象,轻量、高效,是最常用的智能指针。

核心特性
  • 不支持拷贝(copy),仅支持移动(move);
  • 析构时直接释放托管的内存;
  • 体积与原始指针一致,无额外性能开销。
实战用法
#include <iostream>
#include <memory> // 必须包含的头文件
using namespace std;

class MyClass {
public:
    MyClass(int val) : value(val) {
        cout << "MyClass 构造:" << value << endl;
    }
    ~MyClass() {
        cout << "MyClass 析构:" << value << endl;
    }
    void show() {
        cout << "值:" << value << endl;
    }
private:
    int value;
};

int main() {
    // 方式1:推荐!用make_unique创建(C++14及以上支持)
    unique_ptr<MyClass> ptr1 = make_unique<MyClass>(10);
    ptr1->show(); // 调用成员函数,用法与原始指针一致

    // 方式2:直接构造(不推荐,存在异常安全风险)
    // unique_ptr<MyClass> ptr1(new MyClass(10));

    // 禁止拷贝(编译报错)—— 保证独占性
    // unique_ptr<MyClass> ptr2 = ptr1; 

    // 移动语义:转移所有权
    unique_ptr<MyClass> ptr2 = move(ptr1);
    if (ptr1 == nullptr) { // ptr1失去所有权,变为空
        cout << "ptr1 已无所有权" << endl;
    }
    ptr2->show();

    // 手动释放(一般无需手动调用,析构时自动释放)
    ptr2.reset(); // 释放内存,ptr2变为空

    return 0; // 若ptr2未reset,此处析构仍会自动释放
}
输出结果
MyClass 构造:10
值:10
ptr1 已无所有权
值:10
MyClass 析构:10
适用场景
  • 单个对象 / 数组的独占式管理(如函数内的局部动态对象、返回动态对象);
  • 容器中存储动态对象(如vector<unique_ptr<MyClass>>);
  • 替代原始指针,作为类的成员变量管理动态资源。

2. std::shared_ptr:共享式内存管理

shared_ptr共享所有权的智能指针,多个shared_ptr可指向同一个对象,通过 “引用计数” 管理内存:

  • 新增一个shared_ptr指向对象,引用计数 + 1;
  • 销毁一个shared_ptr,引用计数 - 1;
  • 引用计数为 0 时,自动释放对象内存。
核心特性
  • 支持拷贝和赋值,引用计数自动更新;
  • 可通过use_count()查看当前引用计数;
  • 相比unique_ptr有轻微性能开销(维护引用计数)。
实战用法
#include <iostream>
#include <memory>
using namespace std;

void useSharedPtr(shared_ptr<MyClass> ptr) {
    cout << "函数内引用计数:" << ptr.use_count() << endl; // 3
    ptr->show();
}

int main() {
    // 推荐用make_shared创建(更高效、更安全)
    shared_ptr<MyClass> ptr1 = make_shared<MyClass>(20);
    cout << "初始引用计数:" << ptr1.use_count() << endl; // 1

    // 拷贝,引用计数+1
    shared_ptr<MyClass> ptr2 = ptr1;
    cout << "拷贝后引用计数:" << ptr1.use_count() << endl; // 2

    // 传递给函数(值传递,引用计数+1)
    useSharedPtr(ptr1);

    // 函数结束,临时对象销毁,引用计数回到2
    cout << "函数后引用计数:" << ptr1.use_count() << endl; // 2

    // 释放其中一个指针,引用计数-1
    ptr1.reset();
    cout << "释放ptr1后计数:" << ptr2.use_count() << endl; // 1

    return 0; // ptr2析构,计数归0,内存释放
}
输出结果
MyClass 构造:20
初始引用计数:1
拷贝后引用计数:2
函数内引用计数:3
值:20
函数后引用计数:2
释放ptr1后计数:1
MyClass 析构:20
适用场景
  • 多个对象需要共享同一个动态资源(如多线程共享数据、复杂数据结构的节点共享);
  • 无法确定谁最后释放资源的场景。

3. std::weak_ptr:解决循环引用的 “观察者”

weak_ptrshared_ptr的辅助类型,不拥有资源所有权,仅作为 “观察者” 存在:

  • 不会增加引用计数;
  • 可检测所指向的资源是否还存在;
  • 核心作用:解决shared_ptr的循环引用问题。
循环引用问题(反面例子)
// 循环引用导致内存泄漏
class A;
class B;

class A {
public:
    shared_ptr<B> b_ptr;
    ~A() { cout << "A析构" << endl; }
};

class B {
public:
    shared_ptr<A> a_ptr;
    ~B() { cout << "B析构" << endl; }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b_ptr = b; // A引用B
    b->a_ptr = a; // B引用A
    // 引用计数均为2,析构时计数仅减到1,内存泄漏
    return 0;
}
输出结果(无析构输出,内存泄漏)
(无任何析构打印,A和B的内存未释放)
用 weak_ptr 解决循环引用
class A {
public:
    weak_ptr<B> b_ptr; // 改为weak_ptr
    ~A() { cout << "A析构" << endl; }
};

class B {
public:
    weak_ptr<A> a_ptr; // 改为weak_ptr
    ~B() { cout << "B析构" << endl; }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;
    // weak_ptr不增加计数,析构时计数归0,内存释放
    return 0;
}
输出结果
A析构
B析构
weak_ptr 核心用法
#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<MyClass> sp = make_shared<MyClass>(30);
    weak_ptr<MyClass> wp = sp; // 弱引用指向sp

    cout << "sp计数:" << sp.use_count() << endl; // 1(wp不增加计数)
    cout << "wp是否过期:" << boolalpha << wp.expired() << endl; // false

    // 访问弱引用指向的对象:先lock转为shared_ptr
    if (shared_ptr<MyClass> temp = wp.lock()) {
        temp->show(); // 安全访问
    }

    sp.reset(); // 释放sp,对象销毁
    cout << "wp是否过期:" << boolalpha << wp.expired() << endl; // true

    return 0;
}
适用场景
  • 打破shared_ptr的循环引用;
  • 缓存场景(如缓存对象是否存在,存在则使用,不存在则重建)。

四、智能指针避坑指南

  1. 优先使用 make_unique/make_shared避免直接用new构造智能指针(如unique_ptr<MyClass> ptr(new MyClass())),make_unique/make_shared更高效,且能避免异常安全问题。

  2. 禁止混用智能指针和原始指针不要将同一个原始指针交给多个智能指针管理(会导致重复释放),也不要用原始指针释放智能指针托管的内存。

  3. 不要管理非动态内存智能指针析构时会调用delete,若指向栈内存(如局部变量),会导致未定义行为:

    int a = 10;
    unique_ptr<int> ptr(&a); // 错误!指向栈内存
  4. 避免 shared_ptr 的循环引用只要两个shared_ptr互相引用,就会导致引用计数无法归 0,必须用weak_ptr打破循环。

五、总结

  1. 智能指针的核心是 RAII 机制:构造获取内存,析构自动释放,杜绝手动管理的疏漏;
  2. unique_ptr:独占所有权,轻量高效,优先作为默认选择;
  3. shared_ptr:共享所有权,通过引用计数管理,适用于多对象共享资源;
  4. weak_ptr:不拥有所有权,仅作为shared_ptr的辅助,解决循环引用问题;
  5. 使用时优先选择make_unique/make_shared,避免混用原始指针,警惕循环引用。
Logo

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

更多推荐