C++各种智能指针的使用小结

 更新时间:2026年07月16日 09:02:35   作者:华丽的周遭  
本文主要介绍了C++各种智能指针的使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

C++智能指针是RAII(资源获取即初始化)思想的核心实践,用于自动管理动态内存,解决传统裸指针的内存泄漏悬垂指针重复释放等问题。

C++11及后续标准提供了多种智能指针,核心区别在于所有权模型(独占/共享/弱引用),定义在<memory> 头文件中。

一、智能指针的分类与核心特性

C++标准库中的智能指针主要有4种(auto_ptr已被废弃):

类型

所有权模型

核心特点

auto_ptr

独占(已废弃)

拷贝时转移所有权,易导致意外失效,禁止使用​

unique_ptr

独占所有权

同一时间仅一个指针拥有对象,不可拷贝,仅可移动​

shared_ptr

共享所有权

多个指针共享对象,通过引用计数跟踪所有者数量,计数归零时释放对象

weak_ptr

弱引用(无所有权)

指向shared_ptr管理的对象,但不增加引用计数,用于解决循环引用

二、各智能指针详解

1.unique_ptr:独占所有权的轻量级指针

unique_ptr最常用的智能指针之一,适用于单一所有者的场景(如动态数组、局部资源管理)。

核心特性:

  • 独占性:不能拷贝(copy constructor/copy assignment被删除),只能通过std::move转移所有权。
  • 轻量:大小与裸指针相同(无额外开销,除非自定义删除器)。
  • 支持数组unique_ptr<T[]>重载了operator[],自动用delete[]释放。
  • 自定义删除器:可指定任意资源释放逻辑(如文件句柄、网络连接)。

用法示例:

#include <memory>
#include <iostream>

int main() {
    // 1. 创建:优先用make_unique(C++14引入,更安全高效)
    std::unique_ptr<int> p1 = std::make_unique<int>(42); 
    // 等价于:std::unique_ptr<int> p1(new int(42));

    // 2. 访问对象:用*或->
    std::cout << *p1 << std::endl; // 输出42

    // 3. 移动所有权(不可拷贝)
    std::unique_ptr<int> p2 = std::move(p1); // p1失去所有权,变为nullptr
    if (!p1) std::cout << "p1 is null" << std::endl;

    // 4. 数组版本
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(3);
    arr[0] = 1; arr[1] = 2; arr[2] = 3;

    // 5. 释放所有权:release()返回裸指针,不再管理
    int* raw = p2.release();
    delete raw; // 需手动释放(不推荐,尽量用智能指针全程管理)

    // 6. 重置:reset()释放当前对象,可选新对象
    p2.reset(new int(100)); // 先释放原对象(若有),再管理新对象
    p2.reset(); // 释放对象,变为nullptr

    return 0;
}

自定义删除器示例(管理文件句柄):

#include <cstdio>
#include <memory>

// 自定义删除器:关闭文件
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) std::fclose(fp);
    }
};

int main() {
    // 用自定义删除器创建unique_ptr
    std::unique_ptr<FILE, FileDeleter> fp(std::fopen("test.txt", "w"));
    if (fp) std::fputs("hello", fp.get()); // get()返回裸指针
    // 离开作用域时,自动调用FileDeleter释放文件
    return 0;
}

2.shared_ptr:共享所有权的引用计数指针

shared_ptr适用于多所有者的场景(如容器中存储动态对象、跨模块共享资源)。

核心特性:

  • 共享性:多个shared_ptr可指向同一对象,通过引用计数跟踪所有者数量。
  • 引用计数:每次拷贝shared_ptr,计数+1;析构或reset()时,计数-1;计数归零时,释放对象。
  • 高效创建:优先用std::make_shared(C++11引入),一次性分配对象内存控制块(含引用计数、弱引用计数),比直接new更高效。
  • 线程安全:引用计数的增减是原子操作(多线程安全),但对象本身的访问需额外同步(如互斥锁)。

用法示例:

#include <memory>
#include <iostream>

int main() {
    // 1. 创建:优先用make_shared(更高效)
    auto p1 = std::make_shared<int>(42); 
    std::cout << "use_count: " << p1.use_count() << std::endl; // 输出1

    // 2. 共享所有权:拷贝时计数+1
    auto p2 = p1; 
    std::cout << "use_count: " << p1.use_count() << std::endl; // 输出2

    // 3. 释放所有权:reset()减少计数
    p1.reset(); 
    std::cout << "use_count after p1.reset(): " << p2.use_count() << std::endl; // 输出1

    // 4. 自定义删除器(管理动态数组)
    auto arr = std::shared_ptr<int>(new int[3], [](int* p) { delete[] p; });
    arr.get()[0] = 1; // 访问数组元素

    return 0;
}

注意:make_sharedVS 直接new

make_shared的优势:

  • 更安全:避免裸指针暴露(如func(std::shared_ptr<int>(new int), func2())可能因编译器优化导致内存泄漏);
  • 更高效:一次性分配对象和控制块,减少内存碎片;
  • 异常安全:构造过程中若抛出异常,make_shared不会泄漏内存。

例外情况:需要自定义删除器延迟初始化时,只能用new

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

weak_ptrshared_ptr的“观察者”,不拥有对象所有权,仅指向shared_ptr管理的对象,用于解决循环引用问题。

核心问题:循环引用

当两个shared_ptr互相指向对方时,引用计数永远无法归零,导致内存泄漏

#include <memory>

struct A { std::shared_ptr<B> b; };
struct B { std::shared_ptr<A> a; };

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b = b; // A的b指向B,B的计数+1(变为2)
    b->a = a; // B的a指向A,A的计数+1(变为2)
    // 离开作用域时,a和b的计数各减1(变为1),无法释放!
    return 0;
}

weak_ptr的解决方案

将其中一个shared_ptr改为weak_ptr(弱引用,不增加计数):

struct A { std::weak_ptr<B> b; }; // 弱引用,不增加B的计数
struct B { std::weak_ptr<A> a; }; // 弱引用,不增加A的计数

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b = b; // B的计数仍为1(weak_ptr不增加)
    b->a = a; // A的计数仍为1(weak_ptr不增加)
    // 离开作用域时,a和b的计数各减1(变为0),正常释放!
    return 0;
}

weak_ptr的用法:

  • 创建:由shared_ptr构造,或通过lock()获取shared_ptr
  • 访问对象:必须用lock()转换为shared_ptr(若对象已释放,返回空shared_ptr)。
  • 检查有效性:用expired()判断对象是否已释放(use_count() == 0)。

示例:

#include <memory>
#include <iostream>

int main() {
    auto sp = std::make_shared<int>(42);
    std::weak_ptr<int> wp = sp; // 弱引用指向sp的对象

    // 1. 检查有效性
    if (!wp.expired()) {
        std::cout << "Object exists" << std::endl;
    }

    // 2. 访问对象:lock()获取shared_ptr
    auto locked_sp = wp.lock();
    if (locked_sp) {
        std::cout << *locked_sp << std::endl; // 输出42
    }

    // 3. 释放原对象
    sp.reset();
    if (wp.expired()) {
        std::cout << "Object has been released" << std::endl;
    }

    return 0;
}

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

  • 禁止用裸指针初始化多个智能指针
    • 会导致多个智能指针独立管理同一对象,析构时重复释放
    • ❌ 错误:int* p = new int; auto p1 = std::shared_ptr<int>(p); auto p2 = std::shared_ptr<int>(p);
    • ✅ 正确:auto p1 = std::make_shared<int>(42); auto p2 = p1;
  • 优先用make_shared/make_unique:
    • 避免裸指针暴露,提升安全性和效率(见前文)。
  • 避免循环引用
    • weak_ptr打破shared_ptr之间的双向依赖(见前文)。
  • 不要手动delete智能指针的get()结果:
    • get()返回的裸指针是智能指针内部管理的,手动delete会导致双重释放
  • 线程安全
    • shared_ptr引用计数是线程安全的(原子操作);
    • shared_ptr对象本身不是线程安全的(多线程访问需同步);
    • unique_ptr不支持多线程(独占性)。

四、智能指针的选择指南

场景

推荐智能指针

单一所有者(如局部变量)

unique_ptr

多所有者(如容器共享)

shared_ptr

解决循环引用

weak_ptr

管理数组

unique_ptr<T[]>

管理非内存资源(文件、socket)

带自定义删除器的unique_ptr/shared_ptr

五、总结

智能指针是C++现代编程的必备工具,核心是通过所有权模型自动管理内存。记住:

  • 独占用unique_ptr,共享用shared_ptr
  • 循环引用用weak_ptr破局;
  • 优先用make_shared/make_unique
  • 避免裸指针与智能指针混用。

掌握智能指针能大幅减少内存错误,提升代码的健壮性和可维护性——这也是嵌入式Linux开发中(如驱动、Qt应用、流媒体资源管理)的关键技能。

到此这篇关于C++各种智能指针的使用小结的文章就介绍到这了,更多相关C++各种智能指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • c语言实现把文件中数据读取并存到数组中

    c语言实现把文件中数据读取并存到数组中

    下面小编就为大家带来一篇c语言实现把文件中数据读取并存到数组中。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • C++17中的std::from_chars函数使用及说明

    C++17中的std::from_chars函数使用及说明

    这篇文章主要介绍了C++17中的std::from_chars函数使用及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-10-10
  • C++实现职工工资管理系统

    C++实现职工工资管理系统

    这篇文章主要为大家详细介绍了C++实现简单的职工工资管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • 基于C/C++时间函数的使用详解

    基于C/C++时间函数的使用详解

    本篇文章是对C/C++时间函数的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 浅谈使用Rapidxml 库遇到的问题和分析过程(分享)

    浅谈使用Rapidxml 库遇到的问题和分析过程(分享)

    下面小编就为大家带来一篇浅谈使用Rapidxml 库遇到的问题和分析过程(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • VS2019配置BOOST的方法(v1.70.0库)

    VS2019配置BOOST的方法(v1.70.0库)

    这篇文章主要介绍了VS2019配置BOOST的方法(v1.70.0库),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • C++异常机制:栈展开、异常传播与异常安全

    C++异常机制:栈展开、异常传播与异常安全

    本文主要介绍了C语言和C++处理错误的方式,C语言提供了终止程序、返回错误码、errno等方式,而C++引入了异常处理机制,通过throw抛出异常,通过try-catch捕获和处理异常,异常处理可以提高程序的健壮性
    2026-05-05
  • C语言编程中实现二分查找的简单入门实例

    C语言编程中实现二分查找的简单入门实例

    这篇文章主要介绍了C语言编程中实现二分查找的简单入门实例,需要的朋友可以参考下
    2015-12-12
  • C++高精度算法的使用场景详解

    C++高精度算法的使用场景详解

    在我们进行计算的过程中,经常会遇到几十位,甚至几百位的数字的计算问题,也有可能会遇到小数点后几十位,几百位的情况,而我们面对这样的情况下,long long  和 double 的数据范围显然是不够使用的了。因此这时,我们就需要引入一个新的算法,叫做高精度算法
    2022-09-09
  • C++超详细讲解字符串类

    C++超详细讲解字符串类

    在C++中只有一个字符串类,即basic_string,其管理着一个 '\0' 结尾的字符数组。头文件:#include <string>,string类支持两种输入方式:cin方式和getline()方式
    2022-06-06

最新评论