【C++ 】智能指针:内存管理的 “自动导航仪”

【C++】智能指针:内存管理的“自动导航仪”

在现代 C++(C++11 及以后)中,智能指针是管理动态内存最推荐、最安全的方式。
它们的核心目标是:

  • 自动释放内存(RAII 思想)
  • 防止内存泄漏
  • 避免悬垂指针
  • 减少手动 delete 的出错概率

C++ 标准库提供了三种主要智能指针,它们各有明确的使用场景和权衡。

一、智能指针家族对比表(强烈建议背下来)

智能指针类型头文件所有权语义是否可拷贝是否可移动主要使用场景开销线程安全
std::unique_ptr<T><memory>独占所有权资源唯一拥有者、工厂函数返回值、Pimpl极低(几乎无)
std::shared_ptr<T><memory>共享所有权需要多个对象共享同一资源、容器元素较高(引用计数)否(计数器线程安全)
std::weak_ptr<T><memory>弱引用(不拥有)打破 shared_ptr 循环引用、缓存、观察者极低(不增计数)

二、三大智能指针详解与使用规范

1. std::unique_ptr(独占所有权,最轻量)

特点

  • 同一时刻只有一个 unique_ptr 拥有资源
  • 不可拷贝,只能移动(std::move
  • 析构时自动 delete 指向的对象
  • 开销几乎为零(通常只占一个指针大小)

最常见用法

#include <memory>
#include <iostream>

class Resource {
public:
    ~Resource() { std::cout << "资源被销毁\n"; }
};

int main() {
    // 方式1:推荐的现代写法
    auto ptr = std::make_unique<Resource>();          // C++14+

    // 方式2:显式构造(C++11)
    std::unique_ptr<Resource> p1(new Resource());

    // 转移所有权(非常重要)
    auto p2 = std::move(p1);   // p1 变为空,p2 获得所有权

    // 访问
    if (p2) {
        // 使用 *p2 或 p2.get()
    }

    // 离开作用域自动销毁
}

常用场景

  • 工厂函数返回值
  • RAII 封装文件句柄、socket、锁等
  • Pimpl 模式(私有实现)
  • 作为类成员(独占资源)

注意
不要用 new 后直接赋值给 unique_ptr 裸指针变量,会导致双重释放风险。

错误示范:

Resource* raw = new Resource();
std::unique_ptr<Resource> p1(raw);
std::unique_ptr<Resource> p2(raw);   // 灾难!双重 delete

正确示范:

auto p1 = std::make_unique<Resource>();
auto p2 = std::move(p1);

2. std::shared_ptr(共享所有权,引用计数)

核心机制引用计数(reference counting)

  • 每个 shared_ptr 内部维护两个指针:
  • 指向实际对象的原始指针
  • 指向控制块的指针(存放引用计数、删除器、弱引用计数等)

创建方式对比(非常重要):

// 不推荐(异常不安全)
std::shared_ptr<T> p(new T());

// 推荐(异常安全,性能更好)
auto p = std::make_shared<T>();           // C++11 起,最推荐

引用计数变化规则

操作强引用计数变化弱引用计数变化对象是否销毁
shared_ptr 拷贝构造/赋值+1
shared_ptr 析构-1计数为0时销毁
weak_ptr 构造+1
weak_ptr 析构-1
weak_ptr::lock() 成功+1

经典使用场景

  • 多个对象需要共享同一份资源
  • 放入容器(如 vector<shared_ptr<T>>
  • 树形结构、图结构中的节点共享

循环引用问题(经典面试题):

struct Node {
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "销毁\n"; }
};

int main() {
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();

    n1->next = n2;
    n2->next = n1;   // 循环引用!两个对象都不会被销毁
}

解决办法:用 weak_ptr 打破环

struct Node {
    std::weak_ptr<Node> next;   // 改为 weak_ptr
};

3. std::weak_ptr(观察者,不拥有所有权)

核心作用

  • 不增加强引用计数
  • 不会阻止对象的析构
  • 可通过 lock() 尝试转换为 shared_ptr
  • 常用于缓存、观察者模式、打破循环引用

典型用法

std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;

if (auto locked = wp.lock()) {
    std::cout << *locked << "\n";  // 42
} else {
    std::cout << "对象已销毁\n";
}

三、现代 C++ 智能指针最佳实践总结

  1. 优先使用 std::make_unique / std::make_shared
  2. 需要共享所有权才用 shared_ptr
  3. 独占资源一律用 unique_ptr
  4. weak_ptr 解决循环引用或临时观察
  5. 永远不要把 new 得到的裸指针直接存到多个智能指针中
  6. 容器中存储对象指针时,优先考虑 shared_ptrunique_ptr
  7. 不要在函数参数中使用 shared_ptr 传参(除非明确需要共享所有权)
  • 正确:void func(const Widget&)void func(Widget*)
  • 不推荐:void func(std::shared_ptr<Widget>)(除非函数要保存一份)

四、常见面试高频问题

  1. unique_ptrshared_ptr 的区别?
  2. 为什么 make_sharedshared_ptr(new T) 更好?
  3. weak_ptr 有什么用?如何检测对象是否还活着?
  4. 什么是循环引用?如何用智能指针解决?
  5. shared_ptr 的控制块包含什么信息?
  6. unique_ptr 能否拷贝?如何转移所有权?

如果你想继续深入某个部分,例如:

  • 自定义删除器(deleter)
  • enable_shared_from_this 用法与原理
  • shared_ptrweak_ptr 实现原理(控制块结构)
  • 智能指针在多线程中的正确用法
  • 经典手撕题目:实现简易 shared_ptr

随时告诉我,我可以继续展开详细讲解或代码实现。

文章已创建 4580

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部