前言:智能指针是 C++ 标准库(STL)提供的一种自动管理动态内存的工具,其核心作用是避免手动调用 new/delete 导致的内存泄漏、悬挂指针、重复释放等问题。它通过 “资源获取即初始化(RAII)” 机制,在智能指针的生命周期结束时(如超出作用域)自动释放所管理的内存。

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

手动管理动态内存(new/delete)存在三大风险:

  1. 内存泄漏:忘记调用 delete,导致内存永远无法释放。
  2. 重复释放:对同一指针多次调用 delete,导致程序崩溃。
  3. 悬挂指针:指针指向的内存已被释放,但指针未置空,后续访问导致未定义行为。

示例(手动管理的问题)

void func() {
    int* ptr = new int(10);  // 动态分配
    if (some_condition) {
        return;  // 忘记delete,内存泄漏
    }
    delete ptr;  // 若上面return,此处不执行
}

智能指针通过自动释放内存解决这些问题,即使函数提前返回或抛出异常,也能保证内存被正确释放。

二、C++ 中的三种智能指针

C++11 及后续标准定义了三种常用智能指针,均位于 <memory> 头文件中:

  • std::unique_ptr:独占所有权的智能指针(唯一拥有对象)。
  • std::shared_ptr:共享所有权的智能指针(多人共享对象)。
  • std::weak_ptr:辅助 shared_ptr 的弱引用指针(不影响所有权)。

1. std::unique_ptr:独占所有权

核心特点

  • 同一时间,只有一个 unique_ptr 能拥有对象的所有权
  • 无法复制(copy),只能移动(move),确保所有权唯一。
  • 当 unique_ptr 销毁时(如超出作用域),自动释放所管理的对象。

用法示例

#include <memory>  // 必须包含的头文件
#include <iostream>
using namespace std;

int main() {
    // 方式1:用new初始化(不推荐,尽量用make_unique)
    unique_ptr<int> up1(new int(10));
    
    // 方式2:用make_unique创建(C++14+,更安全高效)
    unique_ptr<int> up2 = make_unique<int>(20);  // 推荐!
    
    // 访问对象:用*或->(类似普通指针)
    cout << *up1 << endl;  // 输出10
    cout << *up2 << endl;  // 输出20
    
    // 所有权转移(用move,原指针变为nullptr)
    unique_ptr<int> up3 = move(up2);  // up2失去所有权,变为空
    if (up2 == nullptr) {
        cout << "up2 is null" << endl;  // 输出此句
    }
    
    // 无需手动delete,超出作用域时自动释放
    return 0;
}

适用场景

 
  • 管理单个对象的所有权(如局部动态对象、函数返回动态对象)。
  • 作为容器元素(避免复制,适合 vector<unique_ptr<T>>)。
  • 替代 auto_ptrauto_ptr 已被弃用,因存在复制语义缺陷)。

2. std::shared_ptr:共享所有权

核心特点

  • 多个 shared_ptr 可共享同一对象的所有权,通过引用计数(reference count)跟踪对象被引用的次数。
  • 当最后一个 shared_ptr 销毁时,引用计数变为 0,自动释放对象。
  • 支持复制(复制时引用计数 + 1),也支持移动。

引用计数工作原理

  • 每个 shared_ptr 管理的对象,都对应一个 “控制块”(存储引用计数和其他信息)。
  • 当复制 shared_ptr 时(如 sp2 = sp1),引用计数 + 1。
  • 当 shared_ptr 销毁或重置时,引用计数 - 1。
  • 引用计数为 0 时,控制块和对象内存被释放。

用法示例

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

int main() {
    // 方式1:用new初始化(不推荐)
    shared_ptr<int> sp1(new int(100));
    
    // 方式2:用make_shared创建(推荐,效率更高,一次分配内存)
    shared_ptr<int> sp2 = make_shared<int>(200);
    
    // 复制:引用计数+1
    shared_ptr<int> sp3 = sp2;  // sp2和sp3共享对象,引用计数=2
    
    // 访问对象
    cout << *sp2 << " " << *sp3 << endl;  // 输出200 200
    cout << "引用计数:" << sp2.use_count() << endl;  // 输出2(sp2和sp3)
    
    // sp3销毁(超出作用域),引用计数-1
    {
        shared_ptr<int> sp4 = sp2;
        cout << "sp4存在时计数:" << sp2.use_count() << endl;  // 输出3
    }  // sp4销毁,计数变回2
    
    // 重置sp2:sp2不再拥有对象,计数-1
    sp2.reset();
    cout << "sp2重置后计数:" << sp3.use_count() << endl;  // 输出1(仅剩sp3)
    
    return 0;  // sp1和sp3销毁,计数变为0,对象被释放
}

适用场景

  • 多个对象需要共享同一资源的所有权(如多个指针指向同一动态对象)。
  • 复杂数据结构(如树、图)中,节点可能被多个父节点引用。

3. std::weak_ptr:弱引用(解决循环引用)

核心问题shared_ptr 可能导致循环引用(两个对象互相用 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;  // n1引用n2,n2计数=2
    n2->next = n1;  // n2引用n1,n1计数=2
    
    // 函数结束时,n1和n2计数各减1,变为1(互相引用)
    // 导致Node对象无法释放,内存泄漏(析构函数不执行)
    return 0;
}

weak_ptr 的作用

  • 对 shared_ptr 管理的对象进行弱引用(不增加引用计数)。
  • 可观察对象是否存在,但无法直接访问对象(需先通过 lock() 转为 shared_ptr)。

解决循环引用示例

struct Node {
    weak_ptr<Node> next;  // 用weak_ptr替代shared_ptr
    ~Node() { cout << "Node被释放" << endl; }  // 此时会正常执行
};

int main() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    
    n1->next = n2;  // weak_ptr不增加n2的计数(n2计数仍为1)
    n2->next = n1;  // weak_ptr不增加n1的计数(n1计数仍为1)
    
    // 函数结束时,n1和n2计数减为0,对象被释放
    return 0;
}

weak_ptr 常用操作

shared_ptr<int> sp = make_shared<int>(10);
weak_ptr<int> wp = sp;  // 从shared_ptr创建weak_ptr(不增加计数)

// 检查对象是否存在
if (!wp.expired()) {  // expired():对象已释放则返回true
    // 转为shared_ptr才能访问对象(此时计数+1,确保访问期间对象不被释放)
    shared_ptr<int> temp = wp.lock();
    cout << *temp << endl;  // 输出10
}  // temp销毁,计数-1

适用场景

  • 解决 shared_ptr 的循环引用问题。
  • 缓存、观察者模式等场景(只观察对象,不影响其生命周期)。

三、智能指针的关键注意事项

1、优先使用 make_unique 和 make_shared

  • 避免直接用 new 初始化智能指针(如 unique_ptr<int> p(new int(10))),make_xxx 更安全(避免内存泄漏风险)且高效(make_shared 可一次性分配对象和控制块内存)。
auto up = make_unique<int>(10);  // 推荐
auto sp = make_shared<int>(20);  // 推荐

2、不要用同一原始指针初始化多个智能指针
会导致重复释放内存:

int* raw_ptr = new int(10);
unique_ptr<int> p1(raw_ptr);
unique_ptr<int> p2(raw_ptr);  // 错误!p1和p2会分别释放raw_ptr,导致崩溃

3、不要管理非动态分配的内存
智能指针会自动调用 delete,若管理栈上内存全局内存,会导致未定义行为:

int x = 10;
unique_ptr<int> p(&x);  // 错误!x是栈内存,退出作用域时会被delete两次

4、自定义删除器(处理特殊资源)
智能指针默认用 delete 释放内存,若管理其他资源(如文件句柄、网络连接),可指定自定义删除器:

// 管理文件指针(需要用fclose释放)
auto del = [](FILE* fp) { fclose(fp); };  // 自定义删除器(lambda)
unique_ptr<FILE, decltype(del)> fp(fopen("test.txt", "r"), del);

四、三种智能指针的对比

智能指针 所有权 复制性 引用计数 适用场景
unique_ptr 独占 不可复制,可移动 单个对象的独占管理
shared_ptr 共享 可复制,可移动 多个对象共享同一资源
weak_ptr 无(弱引用) 可复制,可移动 解决 shared_ptr 循环引用

五、总结

智能指针是 C++ 内存管理的核心工具,通过 RAII 机制自动释放资源,避免手动管理的风险:

  • 若资源只需一个所有者,用 unique_ptr(高效、安全)。
  • 若资源需要多个所有者,用 shared_ptr(配合 weak_ptr 解决循环引用)。
  • 永远优先使用 make_unique/make_shared 创建智能指针,避免直接操作原始指针。

掌握智能指针是写出健壮 C++ 代码的关键,尤其在大型项目中能显著减少内存相关 bug。

Logo

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

更多推荐