讲师中心 微信公众号
AI工具推荐 视频效率加速

C++智能指针与容器结合使用方法

落枫小哥_1782

落枫小哥_1782

发布时间:2025-09-18 11:01:01

|

584人浏览过

|

来源于php中文网

原创

答案:C++智能指针与容器结合使用可实现安全的内存管理。通过std::unique_ptr实现独占所有权,确保容器销毁时自动释放资源;用std::shared_ptr实现共享所有权,配合引用计数避免内存泄漏。两者均遵循RAII原则,提升异常安全性和代码清晰度。使用时需注意unique_ptr的移动语义、shared_ptr的循环引用及性能开销,推荐emplace_back和make_unique/make_shared优化构造。

c++智能指针与容器结合使用方法

C++智能指针与容器的结合使用,核心在于理解它们各自的生命周期管理机制,并选择合适的智能指针来适配容器的存储语义。说白了,就是如何让容器安全地管理那些我们通过

new
出来的动态对象,避免内存泄漏,同时又能享受C++标准库容器带来的便利。这其中涉及到的不仅仅是语法糖,更是对资源管理哲学的一种深刻实践。

解决方案

当我们需要在C++容器中存放动态分配的对象时,直接使用裸指针无疑是埋下了一颗定时炸弹。手动管理内存的复杂性,尤其是在异常发生时,极易导致内存泄漏。智能指针的引入,就是为了解决这个痛点,它利用RAII(Resource Acquisition Is Initialization)原则,将资源的生命周期与对象的生命周期绑定,从而实现自动化的内存管理。

具体到实践中,我们通常会根据对象的拥有权语义来选择

std::unique_ptr
或
std::shared_ptr
。

1. 独占所有权场景:使用

std::unique_ptr

立即学习“C++免费学习笔记(深入)”;

如果容器中的每个元素都应该独占它所指向的对象,也就是说,当元素从容器中移除或容器本身被销毁时,它所管理的对象也应该被销毁,那么

std::unique_ptr
是最佳选择。

#include <vector>
#include <memory>
#include <iostream>

class MyObject {
public:
    int id;
    MyObject(int i) : id(i) { std::cout << "MyObject " << id << " created." << std::endl; }
    ~MyObject() { std::cout << "MyObject " << id << " destroyed." << std::endl; }
    void do_something() { std::cout << "MyObject " << id << " doing something." << std::endl; }
};

// 示例:std::vector 存储 std::unique_ptr
std::vector<std::unique_ptr<MyObject>> objects;

// 添加元素
// 方式一:直接创建并移动
objects.push_back(std::make_unique<MyObject>(1));

// 方式二:使用 emplace_back,可能更高效,避免临时对象
objects.emplace_back(std::make_unique<MyObject>(2));

// 方式三:从现有 unique_ptr 移动
auto temp_ptr = std::make_unique<MyObject>(3);
objects.push_back(std::move(temp_ptr)); // temp_ptr 此时为空

// 访问元素
objects[0]->do_something();
objects.back()->do_something();

// 移除元素(例如,移除最后一个)
// 当 unique_ptr 被销毁时,它指向的对象也会被销毁
objects.pop_back(); // MyObject 3 destroyed.

// 容器销毁时,所有 MyObject 都会被销毁
// (示例中省略了 main 函数的结束,但原理一致)

这里要强调的是,

std::unique_ptr
是不可复制的,只能通过移动语义转移所有权。这意味着你不能直接将一个
unique_ptr
赋值给另一个,也不能将其作为函数参数按值传递(除非你打算转移所有权)。在容器中,这通常意味着使用
std::move
或者
emplace_back
配合
std::make_unique
来构造元素。

2. 共享所有权场景:使用

std::shared_ptr

如果多个智能指针可以共同拥有一个对象,并且只有当所有拥有者都放弃所有权时,对象才会被销毁,那么

std::shared_ptr
就是你的选择。这在需要共享数据、实现观察者模式或缓存机制时非常有用。

#include <vector>
#include <memory>
#include <iostream>

class SharedResource {
public:
    int value;
    SharedResource(int v) : value(v) { std::cout << "SharedResource " << value << " created." << std::endl; }
    ~SharedResource() { std::cout << "SharedResource " << value << " destroyed." << std::endl; }
};

// 示例:std::vector 存储 std::shared_ptr
std::vector<std::shared_ptr<SharedResource>> resources;

// 创建一个共享资源
auto s_ptr1 = std::make_shared<SharedResource>(100);

// 添加到容器,s_ptr1 和 resources[0] 现在共享所有权
resources.push_back(s_ptr1);

// 再次添加,s_ptr1, resources[0], resources[1] 都共享所有权
resources.push_back(s_ptr1);

// 也可以直接在容器中创建
resources.emplace_back(std::make_shared<SharedResource>(200));

// 此时,s_ptr1 的引用计数是 3,resources[2] 的引用计数是 1
std::cout << "s_ptr1 ref count: " << s_ptr1.use_count() << std::endl; // 输出 3

// 访问元素
resources[0]->value = 101;
std::cout << "s_ptr1 value: " << s_ptr1->value << std::endl; // 输出 101

// 移除容器中的一个元素
resources.pop_back(); // SharedResource 200 destroyed. (因为它的引用计数降为 0)
std::cout << "s_ptr1 ref count after pop_back: " << s_ptr1.use_count() << std::endl; // 输出 2

// s_ptr1 超出作用域或被重置时,如果它是最后一个拥有者,SharedResource 100 才会销毁。
// (示例中省略了 main 函数的结束)

std::shared_ptr
内部维护了一个引用计数,当这个计数降到零时,它所管理的对象才会被销毁。它的拷贝是廉价的,因为只是复制了指针和增加了引用计数。这使得它在多线程环境下尤其有用,但需要注意,它所管理的对象本身的线程安全性需要额外考虑。

为什么不直接用裸指针存放对象,智能指针能带来哪些实际好处?

说实话,我刚开始学C++的时候,对智能指针这种“额外”的东西是有点抗拒的,觉得裸指针不是挺好用的吗?

new
了就
delete
,多清晰。但真正在复杂的项目里摸爬滚打几年,尤其是在处理异常、多线程或者回调函数这些场景下,才深刻体会到裸指针带来的那种心智负担和潜在的灾难性后果。

直接用裸指针存放对象到容器里,最直接的问题就是内存泄漏。想象一下,你

std::vector<MyObject*>
里塞了一堆
new MyObject()
出来的指针,然后容器可能在某个地方
clear()
了,或者在某个函数返回时被销毁了。如果忘记对每个指针调用
delete
,那些对象就永远地躺在内存里了,直到程序结束。这还不算完,如果容器里存储的指针指向的对象被提前
delete
了,而容器里的指针还在,那么这个指针就成了悬空指针(dangling pointer),后续的访问将是未定义行为,程序随时可能崩溃。

智能指针,尤其是

std::unique_ptr
和
std::shared_ptr
,它们的出现就是为了解决这些“人祸”。它们的核心理念是RAII(Resource Acquisition Is Initialization),简单讲就是“资源在构造时获取,在析构时释放”。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
  • 自动化内存管理: 这是最显著的优点。你把
    std::unique_ptr<MyObject>
    放到容器里,当容器元素被移除、容器被销毁,或者
    unique_ptr
    本身被重置时,它会自动调用其内部裸指针的
    delete
    操作。
    shared_ptr
    也类似,只是它会等到最后一个引用消失时才释放。这意味着我们程序员可以把注意力更多地放在业务逻辑上,而不是繁琐且容易出错的内存管理细节上。
  • 异常安全: 在C++中,如果一个函数在执行过程中抛出异常,那么栈上的局部变量会被正确地销毁。如果这些局部变量是智能指针,它们会负责释放所管理的内存,从而防止内存泄漏。而裸指针则不会,一旦异常抛出,后面的
    delete
    语句可能就永远执行不到了。
  • 避免悬空指针和重复释放:
    unique_ptr
    通过移动语义确保了独占性,一个资源在任何时候都只有一个
    unique_ptr
    拥有。
    shared_ptr
    则通过引用计数来确保资源只在所有拥有者都放弃后才释放,避免了重复释放的风险。虽然
    shared_ptr
    在某些情况下仍可能导致循环引用,但有
    std::weak_ptr
    作为解决方案。
  • 代码意图更清晰: 当你看到
    std::vector<std::unique_ptr<MyObject>>
    时,你立刻就知道这个容器拥有它里面对象的所有权,并且每个对象是独一无二的。而
    std::vector<std::shared_ptr<MyObject>>
    则表明这些对象是共享的,可能有其他地方也持有它们的引用。这种明确的语义表达,对代码的可读性和维护性是极大的提升。

所以,与其说智能指针是“额外”的,不如说它是现代C++中不可或缺的基石,它让我们的代码更健壮、更安全,也更易于理解。

在使用
std::unique_ptr
与容器时,有哪些需要注意的细节和常见误区?

std::unique_ptr
和容器的结合,在我看来是处理对象独占所有权场景的最佳实践。但它毕竟是“独占”的,这就带来了一些使用上的特殊性,如果不注意,很容易踩坑。

首先,最核心的特性是不可复制性。

std::unique_ptr
的设计理念就是“独此一份”,它不允许你通过拷贝构造或拷贝赋值来创建另一个指向相同资源的
unique_ptr
。所以,像
std::vector<std::unique_ptr<MyObject>> vec; auto p = std::make_unique<MyObject>(1); vec.push_back(p);
这样的代码是编译不过的。这是初学者最容易遇到的障碍。

要解决这个问题,你需要显式地进行所有权转移。

  • 使用
    std::move
    :
    当你有一个已经存在的
    unique_ptr
    实例,想把它放入容器,或者从容器中取出来并转移所有权给另一个
    unique_ptr
    ,就必须用
    std::move
    。例如:
    vec.push_back(std::move(p));
    。
    std::move
    本质上是将一个左值强制转换为右值引用,从而允许调用
    unique_ptr
    的移动构造函数或移动赋值运算符。执行后,原来的
    p
    会变成空指针。
  • 使用
    emplace_back
    配合
    std::make_unique
    :
    这是我个人最推荐的方式,因为它通常更高效,避免了不必要的临时对象创建和移动。
    vec.emplace_back(std::make_unique<MyObject>(4));
    。
    emplace_back
    可以直接在容器内部构造元素,而
    std::make_unique
    则负责创建对象并返回一个
    unique_ptr
    ,这个
    unique_ptr
    会直接被移动到容器中。

另一个常见的误区是迭代器失效与所有权转移。当你从容器中删除一个

unique_ptr
元素时,该元素所管理的对象会被自动销毁。这很棒。但如果你想“取出”一个元素,而不是销毁它,你需要一些技巧。例如,要从
std::vector<std::unique_ptr<T>>
中取出第
i
个元素的所有权,你不能直接
auto p = vec[i];
(这会尝试拷贝),而是应该这样做:
auto p = std::move(vec[i]);
。之后,
vec[i]
处的
unique_ptr
就变成了空指针。如果你想从容器中完全移除这个元素,同时保留所有权,你可能需要先
std::move
出来,然后用
vec.erase()
或
vec.remove()
等方法删除空指针占位符。

再者,自定义删除器。

unique_ptr
允许你指定一个自定义的删除器(deleter),这在管理非堆内存资源时非常有用,比如文件句柄、数据库连接等。例如,
std::unique_ptr<FILE, decltype(&fclose)> file_ptr(fopen("test.txt", "w"), &fclose);
。当你将这样的
unique_ptr
放入容器时,容器会正确地管理这个带有自定义删除器的智能指针。

最后,要小心容器内部的排序或重新分配操作。对于

std::vector
,当容量不足发生重新分配时,所有元素都会被移动到新的内存区域。
std::unique_ptr
的移动语义在这里工作得很好,不会有额外开销。但如果你使用像
std::list
或
std::map
这样的容器,它们有不同的内存布局和元素管理方式,但
unique_ptr
的移动语义仍然是其核心。总的来说,理解
unique_ptr
的移动语义是与容器结合使用的关键。它强制你思考所有权的转移,而不是简单的拷贝,这在编写资源安全的代码时非常有益。

何时选择
std::shared_ptr
与容器结合,它又有哪些性能或设计考量?

std::shared_ptr
与容器结合使用,通常发生在我们需要多个地方“共享”一个对象,并且这些地方都对这个对象的生命周期有贡献的场景。我个人觉得,
shared_ptr
的出现,极大地简化了某些复杂设计模式的实现,比如观察者模式、工厂模式返回共享对象,或者缓存管理。

何时选择

std::shared_ptr
:

  • 共享所有权: 这是最核心的场景。如果一个对象不只被一个容器元素拥有,或者被容器元素拥有,同时也被其他独立的智能指针拥有,并且只有当所有拥有者都放弃所有权时,对象才应该被销毁,那么
    shared_ptr
    是唯一选择。
  • 工厂函数返回对象: 当一个工厂函数创建了一个对象,并希望将所有权分配给调用者,但又允许调用者之间共享这个对象时,返回
    std::shared_ptr
    是常见的做法。
  • 缓存机制: 在实现一个缓存系统时,缓存中的对象可能被多个客户端访问。当对象不再被任何客户端引用,并且缓存也决定释放它时,
    shared_ptr
    可以很好地管理其生命周期。
  • 图结构或循环引用(需配合
    std::weak_ptr
    ):
    在构建图结构时,节点之间可能存在相互引用。如果都用
    shared_ptr
    ,容易形成循环引用导致内存泄漏。此时,通常会将其中一个方向的引用设计为
    std::weak_ptr
    ,而容器中可能存储
    std::shared_ptr
    。

性能或设计考量:

虽然

std::shared_ptr
带来了巨大的便利,但它并非没有代价,使用时需要权衡:

  1. 引用计数开销:
    std::shared_ptr
    内部维护了一个引用计数器(通常是原子操作,以支持多线程安全),每次拷贝、赋值或销毁
    shared_ptr
    时,都会涉及这个计数器的增减。原子操作比普通整数操作要慢,这会带来一定的性能开销。对于性能敏感的场景,如果
    unique_ptr
    能满足需求,优先考虑
    unique_ptr
    。
  2. 内存开销:
    std::shared_ptr
    除了存储指向对象的指针外,还需要额外存储一个控制块(control block),这个控制块包含了引用计数、弱引用计数以及自定义删除器等信息。这意味着每个
    shared_ptr
    实例会比裸指针或
    unique_ptr
    占用更多的内存。
  3. 潜在的循环引用: 这是
    shared_ptr
    最大的陷阱之一。如果两个或多个对象通过
    shared_ptr
    相互引用,形成一个环,即使外部不再有对这个环的
    shared_ptr
    引用,环内的对象的引用计数也永远不会降为零,导致内存泄漏。解决办法是使用
    std::weak_ptr
    来打破循环。
    std::weak_ptr
    不增加引用计数,可以观察
    shared_ptr
    所管理的对象,但不会阻止对象的销毁。
  4. 线程安全性:
    std::shared_ptr
    本身的引用计数操作是线程安全的。这意味着在多个线程中同时拷贝、赋值或销毁同一个
    shared_ptr
    实例是安全的。但是,
    std::shared_ptr
    所管理的对象本身的线程安全性不被保证。如果多个线程同时访问或修改
    shared_ptr
    指向的对象,你需要为该对象提供额外的同步机制(如互斥锁)。
  5. std::make_shared
    的优势:
    创建
    shared_ptr
    时,强烈建议使用
    std::make_shared
    而不是
    new
    。
    std::make_shared
    可以一次性分配对象和控制块所需的内存,减少了一次内存分配的开销,并且通常效率更高。例如:
    std::vector<std::shared_ptr<MyObject>> vec; vec.emplace_back(std::make_shared<MyObject>(1));
    。

在我看来,选择

shared_ptr
与容器结合,更多的是一种设计上的考量。它允许你构建更灵活、更具弹性的对象关系,但这种灵活性是以一定的性能和内存开销为代价的。在设计系统时,我总是会先问自己:这个对象真的需要被多个地方共享吗?如果不是,
unique_ptr
通常是更好的选择。如果确实需要共享,那么
shared_ptr
无疑是强大的工具,但一定要警惕循环引用和多线程访问对象本身的安全性问题。

热门AI工具

更多
火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

相关专题

更多
resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.12.20

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6224

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3224

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

1852

2025.10.17

fclose函数的用法
fclose函数的用法

fclose是一个C语言和C++中的标准库函数,用于关闭一个已经打开的文件,是文件操作中非常重要的一个函数,用于将文件流与底层文件系统分离,释放相关的资源。更多关于fclose函数的相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

842

2023.11.30

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

6407

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2688

2023.08.10

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

100

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
php-src源码分析探索
php-src源码分析探索

共6课时 | 0.7万人学习

PHP进阶篇-函数(玉女心经版)
PHP进阶篇-函数(玉女心经版)

共12课时 | 2.1万人学习

最好理解的Promise教程
最好理解的Promise教程

共10课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn