一、什么是智能指针

智能指针是C++用于自动管理内存的一种手段,是RAII(资源获取即初始化)的典型例子,解决了通过new/delete手动管理内存导致的内存泄露、二次释放等问题。

二、智能指针核心原理

智能指针巧妙使用了类的思想,将指针封装成类,利用类的析构函数(自动调用)自动释放内存,无需手动干预。

三、智能指针存在必要

手动管理内存的痛点:

void bad_example() {
    int* p = new int(10); // 动态分配内存
    if (some_condition) {
        return; // 提前返回,未执行 delete,内存泄漏
    }
    delete p; // 正常情况下释放
    // 若后续误写 delete p; 会导致二次释放(崩溃)
    return;
}

智能指针的出现解决了如上问题:无论函数正常返回还是异常退出,都会自动调用析构函数完成内存释放,确保了程序运行安全。

四、C++11三种智能指针

1)unique_ptr:

特点:同一时刻,只允许有一个unique_ptr指向同一对象(独占所有权),不支持拷贝,仅支持移动。

适用场景:管理单个对象/数组,无需共享所有权。

示例

#include <memory>
#include <iostream>

int main() {
    // 1. 管理单个对象(推荐用 make_unique 构造,更安全高效)
    std::unique_ptr<int> up1 = std::make_unique<int>(10); 
    std::cout << *up1 << std::endl; // 解引用:10
    std::cout << up1.get() << std::endl; // 获取原始指针(谨慎使用)

    // 2. 所有权转移(move 后 up1 变为空指针)
    std::unique_ptr<int> up2 = std::move(up1); 
    if (!up1) std::cout << "up1 已为空" << std::endl; // 输出:up1 已为空

    // 3. 管理数组(需指定[],析构时自动调用 delete[])
    std::unique_ptr<int[]> up3 = std::make_unique<int[]>(3);
    up3[0] = 1; up3[1] = 2; up3[2] = 3;

    // 4. 手动释放所有权(reset() 或赋值 nullptr)
    up2.reset(); // 释放 up2 指向的内存,up2 变为空
    up3 = nullptr; // 等价于 reset()

    return 0; // 剩余智能指针自动析构,释放内存
}

注意点:禁止拷贝(std::unique_ptr<int> up4 = up2;编译报错)

2)shared_ptr:

特点:多个shared_ptr可指向同一个对象(共享所有权),增加引用计数来追踪对象被引用的次数。

  •  新增一个share_ptr指向对象时,引用计数+1;
  •   一个shared_ptr析构或重装时,引用计数-1;
  •   当引用计数为0时,自动释放对象内存。

适用场景:多个指针共享同一对象(如多线程共享资源)。

 示例

#include <memory>
#include <iostream>

int main() {
    // 1. 构造(推荐 make_shared,更高效)
    std::shared_ptr<int> sp1 = std::make_shared<int>(30);
    std::cout << "sp1 引用计数:" << sp1.use_count() << std::endl; // 1

    // 2. 共享所有权(引用计数 +1)
    std::shared_ptr<int> sp2 = sp1;
    std::cout << "sp1 引用计数:" << sp1.use_count() << std::endl; // 2
    std::cout << "sp2 引用计数:" << sp2.use_count() << std::endl; // 2

    // 3. 重置一个指针(引用计数 -1)
    sp1.reset();
    std::cout << "sp1 重置后,sp2 引用计数:" << sp2.use_count() << std::endl; // 1

    // 4. 自定义删除器(如管理文件指针、数组等)
    auto delete_func = [](int* p) {
        std::cout << "自定义删除器释放:" << *p << std::endl;
        delete p;
    };
    std::shared_ptr<int> sp3(new int(30), delete_func);

    return 0; // sp2、sp3 析构,sp2 引用计数变为 0 释放内存,sp3 调用自定义删除器
}

注意点

避免循环引用(致命问题):两个shared_ptr互相指向对方,导致引用计数永远不为0,导致内存泄漏。

// 循环引用示例(错误)
struct Node {
    std::shared_ptr<Node> next; // 互相引用
};
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();
a->next = b;
b->next = a; // 循环引用,a 和 b 的引用计数永远为 2,析构时不释放

解决循环引用:weak_ptr(见下文)

3)weak_ptr:

特点

  • 不拥有对象的所有权,仅能“观察”share_ptr指向的对象;
  • 不会增加引用计数,避免循环引用;
  • 无法直接解引用(需要先通过lock()转换为share_ptr,判断对象是否存在)。

适用场景:解决shared_ptr循环引用

示例

#include <memory>
#include <iostream>

struct Node {
    std::weak_ptr<Node> next; // 用 weak_ptr 避免循环引用
};

int main() {
    std::shared_ptr<Node> a = std::make_shared<Node>();
    std::shared_ptr<Node> b = std::make_shared<Node>();

    a->next = b; // weak_ptr 赋值,b 的引用计数仍为 1
    b->next = a; // 无循环引用

    std::cout << "a 引用计数:" << a.use_count() << std::endl; // 1
    std::cout << "b 引用计数:" << b.use_count() << std::endl; // 1

    // 访问 weak_ptr 指向的对象(需 lock() 转换)
    if (std::shared_ptr<Node> temp = a->next.lock()) {
        std::cout << "对象存活" << std::endl; // 输出:对象存活
    }

    a.reset(); // a 析构,引用计数变为 0,释放内存
    if (std::shared_ptr<Node> temp = b->next.lock()) {
        std::cout << "对象存活" << std::endl;
    } else {
        std::cout << "对象已释放" << std::endl; // 输出:对象已释放
    }

    return 0;
}

注意点:通过lock()尝试将weak_ptr转换为shared_ptr,对象存活则返回非空shared_ptr,否则返回空。

五、常见错误避雷

1)循环引用shared_ptr互相引用导致内存泄漏,用weak_ptr解决。

2)裸指针构造智能指针

int* p = new int(10);
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p); // 错误!两个 shared_ptr 管理同一裸指针,会二次释放

解决:始终使用make_shared方法或确保一个裸指针只被一个智能指针管理。

3)智能指针释放后使用原始指针

std::unique_ptr<int> up = std::make_unique<int>(10);
int* p = up.get();
up.reset(); // 释放内存
std::cout << *p; // 错误!p 成为悬垂指针

解决:慎用get()方法,避免长期持有原始指针。

六、总结

总结:智能指针是C++内存管理的“利器”,正确使用可使得程序更加安全、高效。一般优先使用unique_ptr,需要共享时用shared_ptr,循环引用时配合weak_ptr,可解决手动管理内存的痛点。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐