当前位置:首页 > 技术 > 正文内容

C++ 智能指针:unique_ptr、shared_ptr 与 weak_ptr 实战解析

访客 技术 2026年9月5日 1

在 C++ 中,手动管理动态内存极易引发内存泄漏或重复释放。为解决这一问题,标准库引入了智能指针,通过 RAII(资源获取即初始化)机制,在对象生命周期结束时自动释放资源,从而提升程序的异常安全性与资源管理可靠性。

unique_ptr:独占所有权

std::unique_ptr 表示对资源的独占控制,不允许复制,仅支持移动语义。其设计目标是轻量、高效,适用于资源生命周期明确、无需共享的场景,如封装内部实现(pImpl 模式)或作为容器元素。

#include <memory>
#include <iostream>

class Engine {
public:
    void start() const { std::cout << "Engine started.\n"; }
};

// 创建与移动
auto engine1 = std::make_unique<Engine>();
engine1->start();

auto engine2 = std::move(engine1);  // 资源转移,engine1 变为空
if (engine1 == nullptr) {
    std::cout << "engine1 is null.\n";
}

engine2->start();  // 正常使用

// 手动释放
engine2.reset();  // 立即释放资源

unique_ptr 支持数组类型,使用时需显式声明为 unique_ptr<T[]>,此时默认使用 delete[] 释放:

auto buffer = std::make_unique<int[]>(100);
for (int i = 0; i < 100; ++i) {
    buffer[i] = i * 2;
}
// 自动调用 delete[],无需手动干预

shared_ptr:共享所有权与引用计数

std::shared_ptr 通过引用计数实现多个指针共享同一资源。当最后一个 shared_ptr 被销毁或重置时,资源才被释放。适用于多模块共享对象、回调注册等场景。

auto data = std::make_shared<std::string>("Shared data");

auto copy1 = data;     // 引用计数:2
auto copy2 = data;     // 引用计数:3

std::cout << "Use count: " << data.use_count() << "\n";  // 输出 3

copy1.reset();         // 引用计数:2
std::cout << "Use count after reset: " << data.use_count() << "\n";  // 输出 2

但需注意:若多个 shared_ptr 彼此循环引用,将导致内存泄漏:

class Node {
public:
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
};

auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a;  // 循环引用!引用计数永不归零

weak_ptr:打破循环引用

std::weak_ptr 不增加引用计数,仅作为"观察者"访问 shared_ptr 管理的对象。它用于解决循环引用问题,使用前需通过 lock() 转换为临时 shared_ptr 检查对象是否仍存活。

class Node {
public:
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 改为 weak_ptr
};

auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a;  // 不增加 a 的引用计数

if (auto p = b->prev.lock()) {
    std::cout << "Node is still alive.\n";
} else {
    std::cout << "Node has been destroyed.\n";
}

weak_ptr 指向的对象已被释放,调用 lock() 返回空 shared_ptr,或直接构造 shared_ptr 会抛出 std::bad_weak_ptr 异常。

enable_shared_from_this:安全获取 this 的 shared_ptr

当类成员函数需返回指向自身的 shared_ptr 时,不能直接使用 std::shared_ptr<this>,否则会创建多个独立的引用计数,导致双重释放。

继承 std::enable_shared_from_this<T> 后,可通过 shared_from_this() 安全获取共享指针:

#include <memory>

class Task : public std::enable_shared_from_this<Task> {
public:
    void schedule() {
        auto self = shared_from_this();  // 安全获取
        // 传递给异步任务或回调
        std::thread([self]() {
            self->execute();
        }).detach();
    }

    void execute() { std::cout << "Task executed.\n"; }
};

int main() {
    auto task = std::make_shared<Task>();
    task->schedule();  // 安全,不会导致双重释放
}

make_unique 与 make_shared:推荐的构造方式

应优先使用 std::make_uniquestd::make_shared 创建智能指针,而非直接调用 new

  • make_shared 在一次内存分配中同时创建对象和控制块,减少内存碎片,提升性能。
  • make_unique 更安全,避免因异常导致的资源泄漏。
  • 两者均支持构造参数转发,语法简洁。
auto ptr1 = std::make_unique<Engine>();           // 推荐
auto ptr2 = std::make_shared<std::vector<int>>(100, 42);  // 推荐

// 不推荐:可能泄漏
auto bad = std::shared_ptr<Engine>(new Engine());

shared_ptr 与数组:自定义删除器

shared_ptr 默认使用 delete,对数组不适用。若需管理数组,必须指定 std::default_delete<T[]>

auto arr = std::shared_ptr<int[]>(
    new int[50],
    std::default_delete<int[]>()
);

for (int i = 0; i < 50; ++i) {
    arr[i] = i * 3;
}
// 正确调用 delete[],避免未定义行为

现代 C++ 推荐使用 std::vector 替代裸数组,仅在与 C 接口交互或性能关键路径中才使用数组形式的智能指针。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。